Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Vordakk

Multiple sprite recolor question

Recommended Posts

I was thinking of having a wad where there were multiple palette-swaps of a certain enemy. The enemy behavior wouldn't change at all, just color. Like for instance, 4 different types of Cyberdemons, the only difference being color.

The question I have is, is there a way to do this without having to resort to creating a whole new monster in DECORATE? Or is that pretty much what I'm gonna have to do? BTW, the wad is for ZDoom(Doom in Hexen) format.

EDIT: I just realized this is posted in the wrong section. Can a mod move it to the DOOM EDITING section?

Share this post


Link to post

You have several ways of doing this. None are really complicated.

Here's a few examples:

Purely ACS, no new monsters:

Script 10 OPEN
{
	//Super Wizards
	CreateTranslation (1, 145:159=137:142, 1:31=[0,0,0]:[255,0,0],
		36:51=[64,0,0]:[64,0,0], 32:35=[64,0,0]:[64,0,0]);
	Thing_SetTranslation (13, 1);
	SetActorProperty(13, APROP_Health, 360);
	//Purple Wizards
	CreateTranslation (2, 1:31=[0,0,0]:[255,0,255],
		36:51=[64,0,64]:[64,0,64], 32:35=[64,0,64]:[64,0,64]);
	Thing_SetTranslation (17, 2);
}
This is used in a Heretic map (by Captain Toenail BTW) to create two variants of the Disciple of D'Sparil monster. All it requires is giving some TIDs to some of them (here, 13 and 17), then relying on the OPEN script to tweak them.

New monsters in DECORATE:
Actor SuperWizard : Wizard 31337
{
	Health 360
	Translation "145:159=137:142", "1:31=[0,0,0]:[255,0,0]",
		"36:51=[64,0,0]:[64,0,0]", "32:35=[64,0,0]:[64,0,0]"
}
Actor PurpleWizard : Wizard 1337
{
	Translation "1:31=[0,0,0]:[255,0,255]",
		"36:51=[64,0,64]:[64,0,64]", "32:35=[64,0,64]:[64,0,64]"
}
You'll notice that in neither case do you need to be "creating a whole new monster in DECORATE". Inheritance is a powerful thing.

It's up to you whether you prefer to rely on a script or to create new enemy types. The outcome in the map will be the same.


You may want to take a look at Cyb's ZDoom maps, notably the Zort series and Void. They heavily use translated enemies created with ACS. One of the Zort levels even use a looping script to have imps continuously change color.

Share this post


Link to post

Damn, I'm really close. I figured out the ranges and how to express them in ACS, but using the syntax of the above example, I get an error having to do with semicolons. This is what I have.

Script 5 OPEN
{       
        //Blue Dude
	      CreateTranslation (1, 145:152=192:199);
	      Thing_SetTranslation (52, 1);
        //Purple Dude
	      CreateTranslation (2, 145:146=169:169, 147:152=170:176):
	      Thing_SetTranslation (53, 2);
        //Green Dude
	      CreateTranslation (3, 145:152=209:216);
	      Thing_SetTranslation (54, 3);
}
When I hit "compile script", it returns with "Errors! Missing semicolon". The line with the error is supposedly the line with "//Purple Dude" written in it. Any idea?

EDIT: Nevermind, I caught and fixed the problem.

Share this post


Link to post

It was the : instead of ; in the translation line for the purple dude, wasn't it? :p

Share this post


Link to post
Gez said:

It was the : instead of ; in the translation line for the purple dude, wasn't it? :p


Yes indeed, but now I have a further dilemma.

Ok, I'll come clean. The monsters that I want to do palette-swaps on are D'Sparils in Heretic. The script executes fine, but because D'Sparil has TWO forms instead of one, the script only sticks to his first form, when he's riding on the back of the serpent. When he's knocked off of the serpent and arises in his second form, he reverts back to the usual dark reddish D'Sparil :(

So should I try the DECORATE route instead of ACS? Or is there a better way to make that second form's colors adhere?

Share this post


Link to post

I think you'll have to make a DECORATE version of D'Sparil, yeah. Though as always you don't have to make a complete copy. Again, inheritance is a powerful thing.

Actor TranslatableSorcerer : Sorcerer1 replaces Sorcerer1
{
	States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("Sorcerer2", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION)
	}
}
Alright, that's all you need. With this, your D'Sparils will be translated and the foot-going version they'll spawn when they die will get their translation too.

All that's redefined is the Death state. The only real change here is replacing A_SorcererRise with A_UnsetSolid and A_SpawnItemEx.

Share this post


Link to post

I've been messing with this for hours now and it still doesn't work. I placed the decorate monsters, which use the applicable translation lines for their colors and then use the custom death state you posted. I made sure that the monsters are using inheritance. They come out fighting, and the colors look good. The problem is that A_SpawnItemEX never kicks in, so they lay dead forever after you kill their serpents, and none of the Sorcerer2's ever appear.

Do you think that perhaps the "-1" in "SRCR P -1 A_SpawnItemEx" is the problem? I'm at my wits end.

EDIT: Whoa. I just changed that -1 to a 0, and it makes the D'Sparils loop their initial death sequence(the one where the serpent dies) infinitely. Changing it to a 1 does the same thing. Guess that wasn't something I needed to mess with.

Share this post


Link to post

-1 means that they stay in that state forever. 0 means that they stay in that state for 0 tic (the state is run and the next is instantaneously loaded). 1 means that they stay in that state for one tic. In either of the latter case, in the absence of an instruction such as "Stop", "Loop", "Wait" or "Goto", what they'll do next is undefined. Apparently, they looped for you.

Okay, here's something that will work. Guaranteed, I tested it:

Actor TranslatableSorcerer : Sorcerer1 replaces Sorcerer1
{
	States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("RisingSorcerer", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor RisingSorcerer
{
	States
	{
	Spawn:
		SOR2 AB 4
		SOR2 C 4 A_PlaySoundEx("dsparil/rise", "Body")
		SOR2 DEF 4
		SOR2 G 12 A_PlaySoundEx("dsparil/sight", "Body")
		SOR2 G 0 A_SpawnItemEx("Sorcerer2", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}
I added the "no position check" flag so that spawning will be successful even if there's already something in that spot. Normally, the A_UnsetSolid should have made it so that spawning is possible, but who knows.

I also added an intermediate actor, RisingSorcerer. It's a workaround for the fact that A_SorcererRise places the spawned Sorcerer2 in its Rise: state, something we can't do with A_SpawnItemEx. So instead I use a custom actor to play the rise animation, which only at the end does spawn the real Sorcerer2. A drawback is that you can't hurt D'Sparil while he's rising now, whereas you can in normal Heretic.

Share this post


Link to post

Success!!!!!!!!!!!!!!!!!

You know, it feels like it takes an act of Congress to do stuff with DECORATE and ACS, mostly because I know next to nothing about either. I'll bet that if I learned even 25% of either one, I'd be capable of something much deeper and cooler.

Share this post


Link to post

I really hate to keep bringing this issue back up Gez, but I had one last question. The DECORATE now functions nearly flawlessly, but you know when D'Sparil takes a certain amount of damage and decides to warp back to one of his pre-defined warp spots? When this happens, he reverts for a second, during his warping animation, back to his original colors. I'm not sure why...maybe the game treats that warping guy as a separate actor? But do you think there's a way to also somehow change those frames to the colors that were specified in the TRANSLATION command?

Oh, and feel free to tell me if I'm taking this too far :)

Share this post


Link to post

Now this is a lot more complicated to do. The untranslated guy you see is this. To use the same transfer translation trick, one has to recreate in DECORATE code the inner workings of A_Srcr2Decide. It's possible, but tedious.

This might work, haven't had time to test it:

Actor TranslatableSorcerer : Sorcerer1 replaces Sorcerer1
{
	States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("RisingSorcerer", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor RisingSorcerer
{
	States
	{
	Spawn:
		SOR2 AB 4
		SOR2 C 4 A_PlaySoundEx("dsparil/rise", "Body")
		SOR2 DEF 4
		SOR2 G 12 A_PlaySoundEx("dsparil/sight", "Body")
		SOR2 G 0 A_SpawnItemEx("DSparil", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor TranslatTelefade : Sorcerer2Telefade { SeeSound "misc/teleport" }

Actor DSparil : Sorcerer2 replaces Sorcerer2
{
	States
	{
	Missile:
		SOR2 R 0 A_JumpIf((
			(health <=  437 && frandom(1.0, 100.0) <= 75.000) ||
			(health <= 1749 && frandom(1.0, 100.0) <= 46.875) ||
			(health <= 2624 && frandom(1.0, 100.0) <= 25.000) ||
			(health <= 3062 && frandom(1.0, 100.0) <= 12.500) ||
			(health <= 3499 && frandom(1.0, 100.0) <=  6.250)), "DoTeleport")
	LightningAttack:
		SOR2 R 9 
		SOR2 S 9 A_FaceTarget
		SOR2 T 20 A_Srcr2Attack
		Goto See
	DoTeleport:
		SOR2 R 0 A_SpawnItemEx("TranslatTelefade", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		SOR2 R 0 A_ChangeVelocity(0, 0, 0, CVF_REPLACE)
		SOR2 R 0 A_SetAngle(0);
		SOR2 R 9 A_Teleport("Teleport", "BossSpot", "", TF_TELEFRAG)
		Goto See
	}
}

Share this post


Link to post

I'm afraid it doesn't quite work. I had to take out the semicolon in the line, "SOR2 R 0 A_SetAngle(0);" to get it to run, and then it still reverts to the original red Sorcerer2Telefade sprites with no color translations on them.

I played around with it a little bit, and from what I've gathered it may have to do with certain things our buddy here is inherting from SORCERER2. The following is an excerpt from an original SORCERER2 definition.

Teleport:
    SOR2 LKJIHG 6
    Goto See
Is it possible that this is what's screwing us up?

Share this post


Link to post

No, no. That state is there and should be there.


This works for me:

Actor TranslatableSorcerer : Sorcerer1 replaces Sorcerer1
{
	States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("RisingSorcerer", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERPOINTERS|SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor RisingSorcerer
{
	States
	{
	Spawn:
		SOR2 AB 4
		SOR2 C 4 A_PlaySoundEx("dsparil/rise", "Body")
		SOR2 DEF 4
		SOR2 G 12 A_PlaySoundEx("dsparil/sight", "Body")
		SOR2 G 0 A_SpawnItemEx("DSparil", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERPOINTERS|SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor TranslatTelefade : Sorcerer2Telefade { SeeSound "misc/teleport" }

Actor DSparil : Sorcerer2 replaces Sorcerer2
{
	States
	{
	Missile:
		SOR2 R 0 A_JumpIf((
			(health <=  437 && frandom(1.0, 100.0) <= 75.000) ||
			(health <= 1749 && frandom(1.0, 100.0) <= 46.875) ||
			(health <= 2624 && frandom(1.0, 100.0) <= 25.000) ||
			(health <= 3062 && frandom(1.0, 100.0) <= 12.500) ||
			(health <= 3499 && frandom(1.0, 100.0) <=  6.250)), "DoTeleport")
	LightningAttack:
		SOR2 R 9 
		SOR2 S 9 A_FaceTarget
		SOR2 T 20 A_Srcr2Attack
		Goto See
	DoTeleport:
		SOR2 R 0 A_SpawnItemEx("TranslatTelefade", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		SOR2 R 0 A_ChangeVelocity(0, 0, 0, CVF_REPLACE)
		SOR2 R 0 A_SetAngle(0)
		SOR2 R 9 A_Teleport("Teleport", "BossSpot", "", TF_TELEFRAG)
		Goto See
	}
}

Actor RedDude : TranslatableSorcerer {}
Actor BlueDude : TranslatableSorcerer { Translation "145:152=192:199" }
Actor PurpleDude : TranslatableSorcerer { Translation "145:146=169:169", "147:152=170:176" }
Actor GreenDude : TranslatableSorcerer { Translation "145:152=209:216" }
Actor YellowDude : TranslatableSorcerer { Translation "145:152=137:144" }
Actor GreyDude : TranslatableSorcerer { Translation "145:152=54:61" }
Actor TanDude : TranslatableSorcerer { Translation "145:152=75:82" }
I've been summoning the various colored dudes in E3M8, and they keep their same colors all the time.

Share this post


Link to post

Thanks for respondng for the umpteenth time :)

I'm using the guides to try to decipher all this, and so far I get most of what you did and why. But just so I can eventually learn what's going on here, what purpose did using the SXF_TRANSFERPOINTERS flag serve as far as color goes? And why is "{ SeeSound "misc/teleport" }" placed beside the "Actor TranslatTelefade : Sorcerer2Telefade" instead of below it?

Share this post


Link to post

I added the transfer pointer thing because I noticed that sometimes, if I wasn't in its line of sight and didn't make noise, the risen sorcerer wouldn't go after me. It didn't see me or hear me after spawning, after all. It was a bit dumb, so I added it to make it inherit its target from when it was riding its lizard.

As for the question about SeeSound, I usually write actors like this if they have only one property. See also all my colored dudes. Makes for more compact code since they take one line instead of three. Of course when there are more than one property it can become illegible so I'm back on using different lines for each of course; and you also need separate lines for states of they won't be parsed correctly. But yeah, code-wise, it changes nothing.

Share this post


Link to post
Gez said:

I added the transfer pointer thing because I noticed that sometimes, if I wasn't in its line of sight and didn't make noise, the risen sorcerer wouldn't go after me.

Yeah I noticed that too, but didn't know how to fix it. That helps out a lot actually. That's good news.

The bad news is that this still doesn't work for me. When they teleport after getting pounded with a Phoenix Rod shot, it's back to a red suit, no matter what color they were before. Those translations just aren't getting transfered to the TranslatTelefade guy. Perhaps the smartest thing is for me to show you what I'm doing so you can critique it. Here's what I have currently.

Actor BlueD'Sparil : Sorcerer1 1666
{
	Translation "145:152=192:199"
        States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("RisingSorcerer", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERPOINTERS|SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor YellowD'Sparil : Sorcerer1 1667
{
	Translation "145:152=137:144"
	States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("RisingSorcerer", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERPOINTERS|SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor GreenD'Sparil : Sorcerer1 1668
{
	Translation "145:152=209:216"
	States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("RisingSorcerer", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERPOINTERS|SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor RedD'Sparil : Sorcerer1 1669
{
	States
	{
	Death:
		SRCR E 7
		SRCR F 7 A_Scream
		SRCR G 7
		SRCR HIJK 6
		SRCR L 25 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 20 A_PlaySoundEx("dsparil/zap", "body")
		SRCR MN 5
		SRCR O 4
		SRCR L 12
		SRCR P 0 A_UnsetSolid
		SRCR P -1 A_SpawnItemEx("RisingSorcerer", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERPOINTERS|SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor RisingSorcerer
{
	States
	{
	Spawn:
		SOR2 AB 4
		SOR2 C 4 A_PlaySoundEx("dsparil/rise", "Body")
		SOR2 DEF 4
		SOR2 G 12 A_PlaySoundEx("dsparil/sight", "Body")
		SOR2 G 0 A_SpawnItemEx("DSparil", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERPOINTERS|SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		Stop
	}
}

Actor TranslatTelefade : Sorcerer2Telefade { SeeSound "misc/teleport" }

Actor DSparil : Sorcerer2 replaces Sorcerer2
{
	States
	{
	Missile:
		SOR2 R 0 A_JumpIf((
			(health <=  437 && frandom(1.0, 100.0) <= 75.000) ||
			(health <= 1749 && frandom(1.0, 100.0) <= 46.875) ||
			(health <= 2624 && frandom(1.0, 100.0) <= 25.000) ||
			(health <= 3062 && frandom(1.0, 100.0) <= 12.500) ||
			(health <= 3499 && frandom(1.0, 100.0) <=  6.250)), "DoTeleport")
	LightningAttack:
		SOR2 R 9 
		SOR2 S 9 A_FaceTarget
		SOR2 T 20 A_Srcr2Attack
		Goto See
	DoTeleport:
		SOR2 R 0 A_SpawnItemEx("TranslatTelefade", 0, 0, 0, 0, 0, 0, 0, SXF_TRANSFERTRANSLATION|SXF_NOCHECKPOSITION)
		SOR2 R 0 A_ChangeVelocity(0, 0, 0, CVF_REPLACE)
		SOR2 R 0 A_SetAngle(0)
		SOR2 R 9 A_Teleport("Teleport", "BossSpot", "", TF_TELEFRAG)
		Goto See
	}
}
I did it this way so that the different D'Sparils would have a DoomEd Number, so that they'd appear in DoomBuilder under the DECORATE tab. Then I just place them where I need them.

Share this post


Link to post

Okay, so the reason why it doesn't work for the specific case of D'Sparil being hurt by a phoenix shot or a tomed hellstaff shot is because these weapons are hardcoded to do that. He just teleports away instead of being damaged by them.

Share this post


Link to post
Gez said:

Okay, so the reason why it doesn't work for the specific case of D'Sparil being hurt by a phoenix shot or a tomed hellstaff shot is because these weapons are hardcoded to do that. He just teleports away instead of being damaged by them.


The following is a quote from the DOOM wiki:

D'sparil will also often teleport when he is hit by a Phoenix Rod missile or Hell Staff rain (the game code does a specific check for these weapon hitting him).


So basically, every so often when D'Sparil is attacked by either of these methods he teleports away using a different actor than Sorcerer2Telefade(or no actor at all), thus explaining why we can't get those frames to change color. Ok, I think I got it.

Share this post


Link to post

He actually spawns a Telefade too; it's just that it's spawned with the default function, the one which we replaced and emulated with all these lines of code in DECORATE.

Other than replacing the weapons with clones -- which won't necessarily work exactly like the originals since we won't have access to the restricted codepointers they use -- there is no way fix that.

You may try weaseling around it by making sure the player won't have much ammo for the phoenix rod and hellstaff before fighting D'Sparil.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×