How does DECORATE inheritance work?

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
Post Reply
User avatar
shino1
Posts: 61
Joined: Mon Dec 17, 2018 1:18 am

How does DECORATE inheritance work?

Post by shino1 »

So I'm making a new version of an existing enemy. The base version of an enemy has its See state include A_Chase with CHF_FASTCHASE flag. The new version that inherits from it has a new See state that does not have the FASTCHASE flag in in its A_Chase call.

Despite this, the new version still exhibits the FASTCHASE behavior by randomly dashing left and right. I was under impression that if an inheriting actor re-defines a state, the old state is ignored? I know to some degree it works because I gave the new version DOHARMSPECIES actor flag, and it does indeed work. I'm just not sure why the FASTCHASE flag is retained.
Spoiler:
Gez
 
 
Posts: 17946
Joined: Fri Jul 06, 2007 3:22 pm

Re: How does DECORATE inheritance work?

Post by Gez »

The new actor inherits the parent actor's states.

So you defined a new See state. This will affect any instance where the actor will dynamically go to its See state. The problem is when it will go statically to its parent actor's See state. What's the difference? By dynamic, I mean the game has to figure out at runtime what the See state is. A good example of that is A_Look, but if you had something like A_Jump(256, "A_Look"), then it would also be dynamic. By static, I mean instructions like "goto See". Those get baked in at actor compile time (during GZDoom's startup) and will not be updated to figure out if inheritance changed things.

So here's an example, with Doom's Demon actor:

Code: Select all

	States
	{
	Spawn:
		SARG AB 10 A_Look; <-- dynamic go to See, will go to a child's See state as expected
		Loop; <-- static go to Spawn, will only ever go to Demon::Spawn
	See:
		SARG AABBCCDD 2 Fast A_Chase; <-- dynamic go to Melee, will go to a child's Melee or Missile state as expected
		Loop; <-- static go to See, will only ever go to Demon::See
	Melee:
		SARG EF 8 Fast A_FaceTarget;
		SARG G 8 Fast A_SargAttack;
		Goto See; <-- static go to See, will only ever go to Demon::See
	Pain:
		SARG H 2 Fast;
		SARG H 2 Fast A_Pain;
		Goto See; <-- static go to See, will only ever go to Demon::See
	Death:
		SARG I 8;
		SARG J 8 A_Scream;
		SARG K 4;
		SARG L 4 A_NoBlocking;
		SARG M 4;
		SARG N -1;
		Stop;
	Raise:
		SARG N 5;
		SARG MLKJI 5;
		Goto See; <-- static go to See, will only ever go to Demon::See
	}
This is easier to understand when you know that, behind the hood, the "goto", "loop", and "wait" instructions are not actually separate instructions, they're just properties of the preceding state. States form a linked chain, each state having a pointer to its next state. DECORATE and ZScript automatically handle this so you don't have to do tedious boilerplate like in the vanilla code, where you'd have to explicitly tell each state what their next state is instead of letting the engine assume that the next state is the one that comes right after it in the definition, unless otherwise specified with a goto, loop, or wait keyword. But this is explained on the wiki.

TL;DR: if you want your inherited actor to only use its own See state, replace all the "goto See" keywords in the parent actor by "A_Jump(256, "See")" and that will force dynamic jumps to the expected See state.
yum13241
Posts: 875
Joined: Mon May 10, 2021 8:08 pm
Preferred Pronouns: He/Him
Operating System Version (Optional): EndeavorOS (basically Arch)
Graphics Processor: Intel with Vulkan/Metal Support
Contact:

Re: How does DECORATE inheritance work?

Post by yum13241 »

Gez wrote: The new actor inherits the parent actor's states.
And properties/flags, unless you use Skip_Super. This is used in gzdoom.pk3 for the map-maker placeable corpses.
Gez
 
 
Posts: 17946
Joined: Fri Jul 06, 2007 3:22 pm

Re: How does DECORATE inheritance work?

Post by Gez »

Yeah but that wasn't directly relevant to the problem here, and judging by the posted code, already understood by the OP (cf. how some of the properties are redefined, but not the bulk of them).
User avatar
shino1
Posts: 61
Joined: Mon Dec 17, 2018 1:18 am

Re: How does DECORATE inheritance work?

Post by shino1 »

Gez wrote: Tue Feb 28, 2023 10:49 am The new actor inherits the parent actor's states.

So you defined a new See state. This will affect any instance where the actor will dynamically go to its See state. The problem is when it will go statically to its parent actor's See state. What's the difference? By dynamic, I mean the game has to figure out at runtime what the See state is. A good example of that is A_Look, but if you had something like A_Jump(256, "A_Look"), then it would also be dynamic. By static, I mean instructions like "goto See". Those get baked in at actor compile time (during GZDoom's startup) and will not be updated to figure out if inheritance changed things.

So here's an example, with Doom's Demon actor:

Code: Select all

	States
	{
	Spawn:
		SARG AB 10 A_Look; <-- dynamic go to See, will go to a child's See state as expected
		Loop; <-- static go to Spawn, will only ever go to Demon::Spawn
	See:
		SARG AABBCCDD 2 Fast A_Chase; <-- dynamic go to Melee, will go to a child's Melee or Missile state as expected
		Loop; <-- static go to See, will only ever go to Demon::See
	Melee:
		SARG EF 8 Fast A_FaceTarget;
		SARG G 8 Fast A_SargAttack;
		Goto See; <-- static go to See, will only ever go to Demon::See
	Pain:
		SARG H 2 Fast;
		SARG H 2 Fast A_Pain;
		Goto See; <-- static go to See, will only ever go to Demon::See
	Death:
		SARG I 8;
		SARG J 8 A_Scream;
		SARG K 4;
		SARG L 4 A_NoBlocking;
		SARG M 4;
		SARG N -1;
		Stop;
	Raise:
		SARG N 5;
		SARG MLKJI 5;
		Goto See; <-- static go to See, will only ever go to Demon::See
	}
This is easier to understand when you know that, behind the hood, the "goto", "loop", and "wait" instructions are not actually separate instructions, they're just properties of the preceding state. States form a linked chain, each state having a pointer to its next state. DECORATE and ZScript automatically handle this so you don't have to do tedious boilerplate like in the vanilla code, where you'd have to explicitly tell each state what their next state is instead of letting the engine assume that the next state is the one that comes right after it in the definition, unless otherwise specified with a goto, loop, or wait keyword. But this is explained on the wiki.

TL;DR: if you want your inherited actor to only use its own See state, replace all the "goto See" keywords in the parent actor by "A_Jump(256, "See")" and that will force dynamic jumps to the expected See state.
Hi, sorry for the late reply but that actually explains it! Thank you so much!

yum13241 wrote: Mon Mar 06, 2023 9:06 am
Gez wrote: The new actor inherits the parent actor's states.
And properties/flags, unless you use Skip_Super. This is used in gzdoom.pk3 for the map-maker placeable corpses.
I actually didn't know about Skip_Super so I learned something new, thanks!


EDIT: Yep, just checked in the editor and replacing goto with A_Jump fixed it!
Post Reply

Return to “Scripting”