Jump to content

Photo

EWeapon not damaging Link


  • Please log in to reply
11 replies to this topic

#1 Mitchfork

Mitchfork

    no fun. not ever.

  • Contributors
  • Real Name:Mitch
  • Location:Alabama

Posted 17 November 2020 - 12:26 PM

I know I'm basically spamming the forum at this point, but I always get to 99% working and can't figure out the last bit.

 

I'm creating a custom bomb item with a custom explosion sprite.  To do this, I am spawning normal LW_BOMBBLAST lweapons (with sprites set to blank tiles) to damage enemies and trigger bomb flags, and also spawning EW_SCRIPT10 (although this could be anything, I just picked an unused EW_ constant) eweapons that have the actual bomb animation and deal damage to Link.

//Relevant const ints
const int Z4BOMBDAMAGE = 4;
const int Z4BOMBDAMAGESELF = 2;
const int Z4BOMBBLASTEW = EW_SCRIPT10;
const int Z4BOMBBLASTDUR = 32;

//Create the real bomb blasts
lweapon realTL = CreateLWeaponAt(LW_BOMBBLAST, this->X-8, this->Y-8);
	realTL->Damage = Z4BOMBDAMAGE;
	realTL->Z = this->Z;
lweapon realTR = CreateLWeaponAt(LW_BOMBBLAST, this->X+8, this->Y-8);
	realTR->Damage = Z4BOMBDAMAGE;
	realTR->Z = this->Z;
lweapon realBL = CreateLWeaponAt(LW_BOMBBLAST, this->X-8, this->Y+8);
	realBL->Damage = Z4BOMBDAMAGE;
	realBL->Z = this->Z;
lweapon realBR = CreateLWeaponAt(LW_BOMBBLAST, this->X+8, this->Y+8);
	realBR->Damage = Z4BOMBDAMAGE;
	realBR->Z = this->Z;
//Create the visual / hero-damaging bomb blasts
eweapon linkTL = CreateEWeaponAt(Z4BOMBBLASTEW, this->X-8, this->Y-8);
	linkTL->Z = this->Z;
	linkTL->Damage = Z4BOMBDAMAGESELF;
	linkTL->UseSprite(Z4BOMBBLASTTL);
	linkTL->DeadState = Z4BOMBBLASTDUR;
eweapon linkTR = CreateEWeaponAt(Z4BOMBBLASTEW, this->X+8, this->Y-8);
	linkTR->Z = this->Z;
	linkTR->Damage = Z4BOMBDAMAGESELF;
	linkTR->UseSprite(Z4BOMBBLASTTR);
	linkTR->DeadState = Z4BOMBBLASTDUR;
eweapon linkBL = CreateEWeaponAt(Z4BOMBBLASTEW, this->X-8, this->Y+8);
	linkBL->Z = this->Z;
	linkBL->Damage = Z4BOMBDAMAGESELF;
	linkBL->UseSprite(Z4BOMBBLASTBL);
	linkBL->DeadState = Z4BOMBBLASTDUR;		
eweapon linkBR = CreateEWeaponAt(Z4BOMBBLASTEW, this->X+8, this->Y+8);
	linkBR->Z = this->Z;
	linkBR->Damage = Z4BOMBDAMAGESELF;
	linkBR->UseSprite(Z4BOMBBLASTBR);
	linkBR->DeadState = Z4BOMBBLASTDUR;

The lweapons work just fine.  However, these eweapons are not actually doing damage.  Their location, sprite, and DeadState are all correct.  Am I forgetting to set something on these?



#2 ywkls

ywkls

    Master

  • Members

Posted 17 November 2020 - 01:44 PM

So, the first potential problem I see is lines like this.

const int Z4BOMBBLASTDUR = 32;
linkBR->DeadState = Z4BOMBBLASTDUR;

wpn->DeadState is not a counter that decrements every frame and destroys the weapon when it reaches zero.

I know that zscript.txt says otherwise, but it doesn't work reliably that way.

Here are the legal values for wpn->DeadState as of 2.50.2

//Weapon DeadState values. Use with Weapon->DeadState.
const int WDS_NOHIT             = -10; // This value switches collision detection off. Deprecated by weapon->CollDetection.
const int WDS_ALIVE		= -1; // Weapon is currently 'alive'.
const int WDS_DEAD              = 0;  // Use to dispose of most weapons.
const int WDS_BEAMSHARDS        = 23; // Use with LW_BEAMs to shatter it into shards.
const int WDS_ARROW             = 4;  // Use with LW_ARROWs to make them 'wink out' using tile 54.
const int WDS_BOUNCE            = 1;  // Use with LW_BRANGs or LW_HOOKSHOTs to
                                      // make it 'bounce off' and start returning to Link.

The best way to have control over how long an eweapon exists is by using ghost.zh

 

I should add that the first 4 lweapon bomb blasts which you generate will only exist for however many frames their animations last.

If you want them to exist for longer, there are ways to do that. (I personally created a header for just such a purpose. Insert shameless plug here.)

 

I should also add that lines like the following are errors.

lweapon realTL = CreateLWeaponAt(LW_BOMBBLAST, this->X-8, this->Y-8);
realTL->Z = this->Z;

In the first, "this" refers to the lweapon you just made.

Unless you're making it inside an ffc script, that is.

If not, that means you're setting the X and Y to be different from itself?

In the second, it will do nothing.

You're basically setting the Z of the lweapon to equal itself.

Even if you had it in an ffc, it would still do nothing; because ffc's don't have Z coordinates.

Now if you were setting it to equal Link->Z; that would be different.

However, that also isn't really useful; because enemies aren't damaged by lweapons with different Z coordinates.


Edited by ywkls, 17 November 2020 - 01:45 PM.


#3 Mitchfork

Mitchfork

    no fun. not ever.

  • Contributors
  • Real Name:Mitch
  • Location:Alabama

Posted 17 November 2020 - 04:51 PM

So, the first potential problem I see is lines like this.

const int Z4BOMBBLASTDUR = 32;
linkBR->DeadState = Z4BOMBBLASTDUR;
wpn->DeadState is not a counter that decrements every frame and destroys the weapon when it reaches zero.
I know that zscript.txt says otherwise, but it doesn't work reliably that way.
Here are the legal values for wpn->DeadState as of 2.50.2
//Weapon DeadState values. Use with Weapon->DeadState.
const int WDS_NOHIT             = -10; // This value switches collision detection off. Deprecated by weapon->CollDetection.
const int WDS_ALIVE		= -1; // Weapon is currently 'alive'.
const int WDS_DEAD              = 0;  // Use to dispose of most weapons.
const int WDS_BEAMSHARDS        = 23; // Use with LW_BEAMs to shatter it into shards.
const int WDS_ARROW             = 4;  // Use with LW_ARROWs to make them 'wink out' using tile 54.
const int WDS_BOUNCE            = 1;  // Use with LW_BRANGs or LW_HOOKSHOTs to
                                      // make it 'bounce off' and start returning to Link.
The best way to have control over how long an eweapon exists is by using ghost.zh

 


Hmm, that might be it. DeadState is a weird variable.

I did end up re-coding it using ghost.zh eWeapon handling...

//Create the enemy-damage/secret-trigger bomb blasts
lweapon realTL = CreateLWeaponAt(LW_BOMBBLAST, this->X-8, this->Y-8);
	realTL->Damage = Z4BOMBDAMAGE;
	realTL->Z = this->Z;
	realTL->Dir = this->Dir;
lweapon realTR = CreateLWeaponAt(LW_BOMBBLAST, this->X+8, this->Y-8);
	realTR->Damage = Z4BOMBDAMAGE;
	realTR->Z = this->Z;
	realTR->Dir = this->Dir;
lweapon realBL = CreateLWeaponAt(LW_BOMBBLAST, this->X-8, this->Y+8);
	realBL->Damage = Z4BOMBDAMAGE;
	realBL->Z = this->Z;
	realBL->Dir = this->Dir;
lweapon realBR = CreateLWeaponAt(LW_BOMBBLAST, this->X+8, this->Y+8);
	realBR->Damage = Z4BOMBDAMAGE;
	realBR->Z = this->Z;
	realBR->Dir = this->Dir;
//Create the hero-damaging bomb blasts
eweapon linkTL = FireEWeapon(EW_BOMBBLAST,this->X-8,this->Y-8,0,0,Z4BOMBDAMAGESELF,0,0,0);
	linkTL->Z = this->Z;
	SetEWeaponLifespan(linkTL,EWL_TIMER,Z4BOMBBLASTDUR);
eweapon linkTR = FireEWeapon(EW_BOMBBLAST,this->X+8,this->Y-8,0,0,Z4BOMBDAMAGESELF,0,0,0);
	linkTR->Z = this->Z;
	SetEWeaponLifespan(linkTR,EWL_TIMER,Z4BOMBBLASTDUR);	
	SetEWeaponDeathEffect(linkTR,EWD_VANISH,0);	
eweapon linkBL = FireEWeapon(EW_BOMBBLAST,this->X-8,this->Y+8,0,0,Z4BOMBDAMAGESELF,0,0,0);
	linkBL->Z = this->Z;
	SetEWeaponLifespan(linkBL,EWL_TIMER,Z4BOMBBLASTDUR);	
	SetEWeaponDeathEffect(linkBL,EWD_VANISH,0);
eweapon linkBR = FireEWeapon(EW_BOMBBLAST,this->X-8,this->Y+8,0,0,Z4BOMBDAMAGESELF,0,0,0);
	linkBR->Z = this->Z;
	SetEWeaponLifespan(linkBR,EWL_TIMER,Z4BOMBBLASTDUR);	
	SetEWeaponDeathEffect(linkBR,EWD_VANISH,0);
//Create the visual effect
eweapon visualBlast = FireBigEWeapon(Z4BOMBBLASTEW, this->X-8, this->Y-8, DirAngle(this->Dir),0,0,Z4BOMBBLASTSPRITE,0,EWF_NO_COLLISION,2,2);
	visualBlast->Z = this->Z;
	SetEWeaponLifespan(visualBlast,EWL_TIMER,Z4BOMBBLASTDUR);
	SetEWeaponDeathEffect(visualBlast,EWD_VANISH,0);

And this works perfectly with the exception that bomb blasts on the left/top edge of the screen call for the visual effect to be drawn off-screen, which causes it not to appear. I could convert the visual effect to a fastCombo() command to get around that though.
 

I should add that the first 4 lweapon bomb blasts which you generate will only exist for however many frames their animations last.
If you want them to exist for longer, there are ways to do that. (I personally created a header for just such a purpose. Insert shameless plug here.)
 
I should also add that lines like the following are errors.

lweapon realTL = CreateLWeaponAt(LW_BOMBBLAST, this->X-8, this->Y-8);
realTL->Z = this->Z;
In the first, "this" refers to the lweapon you just made.
Unless you're making it inside an ffc script, that is.
If not, that means you're setting the X and Y to be different from itself?
In the second, it will do nothing.
You're basically setting the Z of the lweapon to equal itself.
Even if you had it in an ffc, it would still do nothing; because ffc's don't have Z coordinates.
Now if you were setting it to equal Link->Z; that would be different.
However, that also isn't really useful; because enemies aren't damaged by lweapons with different Z coordinates.

 


These are actually being spawned by an lweapon script, so the "this" is referring to that lweapon.



#4 ywkls

ywkls

    Master

  • Members

Posted 17 November 2020 - 08:42 PM

These are actually being spawned by an lweapon script, so the "this" is referring to that lweapon.


I've never used lweapon scripts, but in my experience shouldn't it then shouldn't it say

lweapon script realTL 

Also, that doesn't make referencing "this" within it nonsensical.

Part of the purpose of the creation of lweapon and eweapon scripts was to make ghost.zh obsolete, but clearly we're a long ways from that.

 

 

And this works perfectly with the exception that bomb blasts on the left/top edge of the screen call for the visual effect to be drawn off-screen, which causes it not to appear. I could convert the visual effect to a fastCombo() command to get around that though.

Alternatively, you could adjust wpn->DrawXOffset, wpn->DrawYOffset and wpn->HitXOffset and wpn->HitYOffset.

More difficult, but not impossible.



#5 Mitchfork

Mitchfork

    no fun. not ever.

  • Contributors
  • Real Name:Mitch
  • Location:Alabama

Posted 17 November 2020 - 09:01 PM

I've never used lweapon scripts, but in my experience shouldn't it then shouldn't it say

lweapon script realTL 

Also, that doesn't make referencing "this" within it nonsensical.

 

The way that it works is that the bomb item spawns a "bomb" (really just an LW_SCRIPT class lweapon).  This is then assigned an lweapon script that handles various parts of its movement and behavior.  Part of that is tracking when its duration is up and creating the explosion (lweapons, eweapons, and graphic) - the explosion effects themselves are not scripted though.  So that code snippet is inside the lweapon script that gets assigned to the bomb, which needs to reference "this" in order to get the position where the explosion should be.  I'm planning on releasing this code as soon as I finish up some other features but with the eweapon issue figured out it works very well I think.

 

EDIT: Also wanted to mention that using Draw*Offset was actually the best way to implement this.  DrawCombo doesn't let you have control over which frame is drawn first (or at least, the frame argument didn't seem to affect anything for me).  Using a sprite is also easier to modify.


Edited by Ebola Zaire, 17 November 2020 - 11:21 PM.


#6 Emily

Emily

    Scripter / Dev

  • ZC Developers

Posted 18 November 2020 - 03:45 AM

The way that it works is that the bomb item spawns a "bomb" (really just an LW_SCRIPT class lweapon).  This is then assigned an lweapon script that handles various parts of its movement and behavior.  Part of that is tracking when its duration is up and creating the explosion (lweapons, eweapons, and graphic) - the explosion effects themselves are not scripted though.  So that code snippet is inside the lweapon script that gets assigned to the bomb, which needs to reference "this" in order to get the position where the explosion should be.  I'm planning on releasing this code as soon as I finish up some other features but with the eweapon issue figured out it works very well I think.

 

EDIT: Also wanted to mention that using Draw*Offset was actually the best way to implement this.  DrawCombo doesn't let you have control over which frame is drawn first (or at least, the frame argument didn't seem to affect anything for me).  Using a sprite is also easier to modify.

The frame argument in DrawCombo literally does nothing. The value is 100% ignored. Why is it there? Great question.

 

You do seem to be using lweapon scripts completely correctly here, and it makes total sense to me. Unsure why the weapon isn't doing damage, though.



#7 Timelord

Timelord

    The Timelord

  • Banned
  • Location:Prydon Academy

Posted 18 November 2020 - 09:01 AM

There are better ways to do this, but I think that the issue is that you created an ewscript type weapon without defining its hitbox. Try adding:
 

linkTL->HitWidth = 16;
linkTL->HitHeight = 16;

 
..and repeat for each of the custom weapons. 
 
I would simplify it to this:
 

	int offsets[]={-8, 8, -8, 8, -8, -8, 8, 8};
	
	for ( int q = 0; q < 4; ++q )
	{
		eweapon bomblink = CreateEWeaponAt(Z4BOMBBLASTEW, offsets[q], offsets[q+4]);
		bomblink->Z = this->Z;
		bomblink->Damage = Z4BOMBDAMAGESELF;
		bomblink->UseSprite(Z4BOMBBLASTBL);
		bomblink->DeadState = Z4BOMBBLASTDUR;
		bomblink->HitWidth = 16;
		bomblink->HitHeight = 16;
		lweapon realbomb = CreateLWeaponAt(LW_BOMBBLAST, offsets[q], offsets[q+4]);
		realbomb->Damage = Z4BOMBDAMAGE;
		realbomb->Z = this->Z;
	}


#8 Mitchfork

Mitchfork

    no fun. not ever.

  • Contributors
  • Real Name:Mitch
  • Location:Alabama

Posted 18 November 2020 - 01:00 PM

So I looked into this much deeper and ywkls is right - if I set the DeadState to my sprite duration, it makes the collision not work.  As soon as I remove that, it hurts again.  This means that I had to create an eweapon script to handle its duration without using ghost.zh. 

 

Considering it more, this probably makes sense with how DeadState is used with engine eWeapons... for most, I think DeadState is -1 until it collides with Link, and then DeadState is set to 0 if they disappear or some positive number if they need to bounce off a shield or wink out, but they are no longer checking collision at that state.  So for scripted lweapons and eweapons, I think I'm safe to use DeadState as a duration as long as collision isn't required.

 

I also had another issue where my shield flags were causing some inconsistency in my testing.  I tried to set the eweapons Level to 1, which ZScript Additions says should make it unblockable, but this didn't seem to work for me.  So I just fix the blast Dir every frame to equal Link's Dir, and that bypasses shields.

 

I did take in Zoria's HitWidth advice to reduce the number of weapons I was generating... but since weapons don't collide if their X/Y is off-screen, I did have to implement some checks for those conditions.

 

If you're curious how this ended up...

//Create the enemy-damage/secret-trigger bomb blasts
lweapon real = CreateLWeaponAt(LW_BOMBBLAST, this->X-8, this->Y-8);
	real->Z = this->Z;
	real->Damage = Z4BOMBDAMAGE;
	real->Dir = this->Dir;
	real->Extend = 3;
	//Account for off-screen detonations
	if (real->X < 0) {
		real->HitWidth = 16;
		real->TileWidth = 1;
		real->X+=16;
	}
	else {real->HitWidth = 32; real->TileWidth = 2; }
	if (real->Y < 0) {
		real->HitHeight = 16;
		real->TileHeight = 1;
		real->Y+=16;
	}
	else {real->HitHeight = 32; real->TileHeight = 2; }
	
//Create the hero-damaging bomb blasts
eweapon lomb = CreateEWeaponAt(Z4BOMBBLASTEW, this->X-8, this->Y-8);
	lomb->Z = this->Z;
	lomb->Damage = Z4BOMBDAMAGESELF;
	lomb->Level = 1; //This doesn't work
	lomb->DrawStyle = DS_CLOAKED;
	lomb->Extend = 3;
	//Account for off-screen detonations
	if (lomb->X < 0) {
		lomb->HitWidth = 16;
		lomb->TileWidth = 1;
		lomb->X+=16;
	}
	else {lomb->HitWidth = 32; lomb->TileWidth = 2; }
	if (lomb->Y < 0) {
		lomb->HitHeight = 16;
		lomb->TileHeight = 1;
		lomb->Y+=16;
	}
	else {lomb->HitHeight = 32; lomb->TileHeight = 2; }
	lomb->Script = Z4BOMBEWSCRIPTSLOT;
	
//Create the visual effect
eweapon visual = CreateEWeaponAt(Z4BOMBBLASTEW, this->X-8, this->Y-8);
	visual->Z = this->Z;
	visual->CollDetection = false;
	visual->Extend = 3;
	visual->TileWidth = 2;
	visual->TileHeight = 2;
	visual->UseSprite(Z4BOMBBLASTSPRITE);
	visual->DeadState = Z4BOMBBLASTDUR;
	//Account for off-screen detonations
	if (visual->X <0) {
		visual->DrawXOffset = visual->X;
		visual->X = 0;
	}
	if (visual->Y <0) {
		visual->DrawYOffset = visual->Y;
		visual->Y = 0;
	}

And the eweapon script that gets assigned to the lomb...

eweapon script z4bombBlast {
	void run() {
		int counter = 0;
		while(true) {
			counter++;
			this->Dir = Hero->Dir;
			if (counter > Z4BOMBBLASTDUR) {
				this->DeadState = WDS_DEAD;
				Quit();
			}
			Waitframe();
		}
	}
}

Thanks everyone for the help.  I really didn't like having ghost.zh as a dependency on this script.


Edited by Ebola Zaire, 18 November 2020 - 01:39 PM.


#9 ywkls

ywkls

    Master

  • Members

Posted 18 November 2020 - 06:44 PM

So I looked into this much deeper and ywkls is right - if I set the DeadState to my sprite duration, it makes the collision not work.  As soon as I remove that, it hurts again.  This means that I had to create an eweapon script to handle its duration without using ghost.zh. 

 

Considering it more, this probably makes sense with how DeadState is used with engine eWeapons... for most, I think DeadState is -1 until it collides with Link, and then DeadState is set to 0 if they disappear or some positive number if they need to bounce off a shield or wink out, but they are no longer checking collision at that state.  So for scripted lweapons and eweapons, I think I'm safe to use DeadState as a duration as long as collision isn't required.

I did a trace on this once, out of curiosity.

The default value of wpn->DeadState is in fact negative one.

This is true for all weapons, as far as I know.

I know you said that you don't want to use ghost.zh, but all it really does is automate the process of handling eweapons every frame.

That's basically what you're doing with the eweapon script, except running a separate script outside of the global.

Also, if you want eweapons to be unblockable; you might want to study the following code.

// Get the unblockable version (8-15) of a direction
int __UnblockableDir(int dir)
{
    if(dir==DIR_UP)
        return 8;
    if(dir==DIR_DOWN)
        return 12;
    if(dir==DIR_LEFT)
        return 14;
    if(dir==DIR_RIGHT)
        return 10;
    if(dir==DIR_LEFTUP)
        return 15;
    if(dir==DIR_RIGHTUP)
        return 9;
    if(dir==DIR_LEFTDOWN)
        return 13;
    if(dir==DIR_RIGHTDOWN)
        return 11;
    
    // Should never get here
    return dir;
}

That's how ghost.zh does it.

I'm not sure how those values work, but they do.



#10 Mitchfork

Mitchfork

    no fun. not ever.

  • Contributors
  • Real Name:Mitch
  • Location:Alabama

Posted 18 November 2020 - 08:39 PM

I did a trace on this once, out of curiosity.

The default value of wpn->DeadState is in fact negative one.

This is true for all weapons, as far as I know.

I know you said that you don't want to use ghost.zh, but all it really does is automate the process of handling eweapons every frame.

That's basically what you're doing with the eweapon script, except running a separate script outside of the global.

 

If it was just for me, I wouldn't have a problem with it.  It's more that I want to submit this to the database and I think it's best practice not to have any unnecessary dependencies.

 

Yeah, that's weird - I have no idea how those unblockable directions work.  Since really dir doesn't matter for this application, I just set it to 10 and that does seem to bypass protections.  I don't think there's any documentation on that in zscript.txt or corresponding std.zh constants.



#11 Saffith

Saffith

    IPv7 user

  • ZC Developers

Posted 19 November 2020 - 05:29 PM

Internally, 8-15 are also used as directions, but they're ordered clockwise starting from up. That simplifies some code for things that adjust their direction rather than moving completely randomly. Weapons using those directions are unblockable because shields don't check for them, but they do knock Link back in the right direction.


  • Mitchfork likes this

#12 Mitchfork

Mitchfork

    no fun. not ever.

  • Contributors
  • Real Name:Mitch
  • Location:Alabama

Posted 19 November 2020 - 09:37 PM

Internally, 8-15 are also used as directions, but they're ordered clockwise starting from up. That simplifies some code for things that adjust their direction rather than moving completely randomly. Weapons using those directions are unblockable because shields don't check for them, but they do knock Link back in the right direction.

Got it - I'll go that way for my solution then.  I have all of my knockback handled via script so did not notice the knockback directionality.




1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users