Solving the MAP07/tag 666 bug with decorate replacer

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!)
User avatar
DevilBlackDeath
Posts: 172
Joined: Fri Sep 06, 2013 2:40 am

Solving the MAP07/tag 666 bug with decorate replacer

Post by DevilBlackDeath »

Hello everyone,

As shown by the bug in some mods concerning MAP07 and the lack of response on the specific topic I created to fix it for Duke Nukem: War Against the Doomed, it feels like there's no widespread solution to the problem. Having just solved it, without adding any ZScript (which should make the solution Zandronum-compatible), I figured I may as well share it with you guys, as well as going into the technicals (although most of you will be able to figure it out).

So, the replacer code for the Mancubus in the DN mod was originally this :

Code: Select all

Actor FatsoReplacer Replaces Fatso
{
	States
	{
	Spawn:
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==1, "Doomy")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==2, "Nazis")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==3, "Aliens")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==4, "All")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==5, "DoomNazis")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==6, "DoomAliens")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==7, "NazisAliens")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==1, "Doomy")
		Stop
	Doomy:
		TNT1 A 0 A_SpawnItemEX("Fatso1",0,0,0,0,0,0)
		Stop
	Nazis:
		TNT1 A 0 A_SpawnItemEX("SuperSoldierSpawner",0,0,0,0,0,0)
		Stop
	Aliens:
		TNT1 A 0 A_SpawnItemEX("FatCommander",0,0,0,0,0,0)
		Stop
	DoomNazis:
		TNT1 A 0 A_SpawnItemEX("Fatso1Nazis",0,0,0,0,0,0)
		Stop
	DoomAliens:
		TNT1 A 0 A_SpawnItemEX("Fatso1Aliens",0,0,0,0,0,0)
		Stop
	NazisAliens:
		TNT1 A 0 A_SpawnItemEX("Fatso1NazisAliens",0,0,0,0,0,0)
		Stop
	All:
		TNT1 A 0 A_SpawnItemEX("Fatso1All",0,0,0,0,0,0)
		Stop
	}
}
It uses such a code because in its option menus you can enable 3 differents kinds of enemies replacer, and you can also activate multiple of them at the same time.

As stated in my previous topic, the problem here is that the hardcoded tag 666 expects the Mancubus actors to die, but, having delved a bit in the source code, I discovered it does not expect the actors that extend Mancubus to die, but either fatso or anything that REPLACES fatso. The obvious issue with this code, is that the replacer is not a monster, and will never be considered dead in any possible way. Actually, it will even be deleted as soon as reaching any Stop keyword, since the duration of all frames before stop is not -1 !

Through trial and error, I found a way to make this actor killable, and actually be killed by the spawned monster on death, without having it affect the gameplay in any way. The new code is as follow :

Code: Select all

Actor FatsoReplacer Replaces Fatso
{
Monster
-SOLID
+BOSSDEATH
+INVULNERABLE
	States
	{
	Death:
		TNT1 A 0 A_BossDeath
		Stop
	XDeath:
		TNT1 A 0 A_BossDeath
		Stop
	Spawn:
		TNT1 A 0 A_NoBlocking
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==1, "Doomy")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==2, "Nazis")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==3, "Aliens")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==4, "All")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==5, "DoomNazis")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==6, "DoomAliens")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==7, "NazisAliens")
		TNT1 A 0 A_JumpIf(CallACS("callmonsterz")==1, "Doomy")
		Goto Death
	Doomy:
		TNT1 A -1 A_JumpIf(!A_SpawnItemEX("Fatso1",0,0,0,0,0,0,0,SXF_SETMASTER|
		SXF_TRANSFERAMBUSHFLAG|SXF_TRANSFERSPECIAL|SXF_CLEARCALLERSPECIAL|
		SXF_CLEARCALLERTID,0,tid), "Death")
		Stop
	Nazis:
		TNT1 A -1 A_JumpIf(!A_SpawnItemEX("SuperSoldierSpawner",0,0,0,0,0,0,0,SXF_SETMASTER|
		SXF_TRANSFERAMBUSHFLAG|SXF_TRANSFERSPECIAL|SXF_CLEARCALLERSPECIAL|
		SXF_CLEARCALLERTID,0,tid), "Death")
		Stop
	Aliens:
		TNT1 A -1 A_JumpIf(!A_SpawnItemEX("FatCommander",0,0,0,0,0,0,0,SXF_SETMASTER|
		SXF_TRANSFERAMBUSHFLAG|SXF_TRANSFERSPECIAL|SXF_CLEARCALLERSPECIAL|
		SXF_CLEARCALLERTID,0,tid), "Death")
		Stop
	DoomNazis:
		TNT1 A -1 A_JumpIf(!A_SpawnItemEX("Fatso1Nazis",0,0,0,0,0,0,0,SXF_SETMASTER|
		SXF_TRANSFERAMBUSHFLAG|SXF_TRANSFERSPECIAL|SXF_CLEARCALLERSPECIAL|
		SXF_CLEARCALLERTID,0,tid), "Death")
		Stop
	DoomAliens:
		TNT1 A -1 A_JumpIf(!A_SpawnItemEX("Fatso1Aliens",0,0,0,0,0,0,0,SXF_SETMASTER|
		SXF_TRANSFERAMBUSHFLAG|SXF_TRANSFERSPECIAL|SXF_CLEARCALLERSPECIAL|
		SXF_CLEARCALLERTID,0,tid), "Death")
		Stop
	NazisAliens:
		TNT1 A -1 A_JumpIf(!A_SpawnItemEX("Fatso1NazisAliens",0,0,0,0,0,0,0,SXF_SETMASTER|
		SXF_TRANSFERAMBUSHFLAG|SXF_TRANSFERSPECIAL|SXF_CLEARCALLERSPECIAL|
		SXF_CLEARCALLERTID,0,tid), "Death")
		Stop
	All:
		TNT1 A -1 A_JumpIf(!A_SpawnItemEX("Fatso1All",0,0,0,0,0,0,0,SXF_SETMASTER|
		SXF_TRANSFERAMBUSHFLAG|SXF_TRANSFERSPECIAL|SXF_CLEARCALLERSPECIAL|
		SXF_CLEARCALLERTID,0,tid), "Death")
		Stop
	}
}
So to explain a bit :
-I added the combo Monster, because anything that's not considered a monster by the game cannot be killed by the A_KillMaster/A_KillChildren directives, so this actor needs to be considered a monster
-I remove the SOLID flag to prevent the player colliding with it (although A_NoBlocking on spawn later is probably enough, if you really want to make your code clean)
-I add the BOSSDEATH flag because it will need to send A_BossDeath signals to alert ZDoom of its death to trigger tag 666 sectors
-I add the INVULNERABLE flag to prevent any possible problems (weapons doing funny stuff that may kill the actor even though it has no physical anchor, we never know)
-I add a Death and XDeath state to properly send A_BossDeath signals
-A_SpawnItemEX gets some extra attributes, namely the flags :
-SXF_SETMASTER : To allow the children to kill its master, the replacer
-SXF_TRANSFERAMBUSHFLAG : To prevent breaking map design behaviours
-SXF_TRANSFERSPECIAL and SXF_CLEARCALLERSPECIAL : To give potential special behaviours to the spawned monster and prevent it to trigger on the replacer
-SXF_CLEARCALLERTID : To prevent any TID-linked event to trigger on the replacer
Then I also added tid at the end to transfer the tid (this is to make any map specific ACS that would trigger events based on TID work properly)
-As a failsafe I embedded A_SpawnItemEX in an A_JumpIf that makes the replacer instantly jump to the Death state if the spawn fails (note I also added such failsafe at the end of the Spawn state, but it's specific to the way the replacer works in DN obviously)

As a result though, monsters must be changed too. In their Death and XDeath state, their second to last frame MUST call

Code: Select all

A_KillMaster("None", KILS_FOILINVUL)
in order to kill the replacer. As a result you can't use base Doom monsters in such a replacer. You would have to create an actor that inherit the base Doom monster you want to use, and have its Death and XDeath state call A_KillMaster then Goto Super::Death and Super::XDeath

I think most enemy trigger problems would be solved with this solution too, if the enemy in question uses a Decorate Replacer. Mind you, the solution works with RandomSpawner, since RandomSpawner transfers everything to the dropped item, including the master.

I probably went a little in detail for some of the better DECORATE coder, but I think it's useful for those who are a bit less at ease with it and would want to use this in their own mod.

Thanks for reading, tell me what you think, don't hesitate to critique the code, enhance it, optimize it, as I am again no DECORATE expert.

Edit : Typos

Edit 2 : It seems QCDE solves this with some ACS, which is much cleaner, although I don't know how exactly ! So as it shows, this is just one solution amongst others.

Return to “Scripting”