Jump to content

[ACS Help] How to Assign TIDs to Initially Placed Monsters in "Doom 2 Format" Map via ACS


Arsinikk

Recommended Posts

Hey Doomworld,

 

I tend to make a lot of ACS patches for Vanilla WADs to get them to work correctly in ZDoom ports, either as singleplayer or cooperative patches. One issue I've always run into when messing with monsters is that I can't really do anything because they don't have a set TID (Thing ID).

 

This makes sense since Doom 2 map format does not allow for things to have tags. Is there anyway for me to give all monsters (placed in the map initially) in a single tagged sector a TID, so that I can actually do something with them via ACS?

 

The specific use case is that in coop on a certain map I'd like to create a sort of "killswitch" that when a player triggers a crusher, the crusher stops and all the enemies in the crusher sector would instead all die from Thing_Destroy.

 

Here's like the example code:

Script "MAP14_Coop_Fix" OPEN {
    int mapLump = GetLevelInfo (LEVELINFO_LEVELNUM);
    if(mapLump == 14){
        If (GameType() != GAME_SINGLE_PLAYER) {
            Spawn("TeleportDest", 2064.0, 3032.0, 0, 0, 180);
            Spawn("TeleportDest", 1312.0, 2200.0, 0, 0, 0);
            SpawnForced("TeleportDest", -1428.0, 1676.0, 0, 0, 64);
            SetCeilingTrigger(152,-1,Ceiling_CrushStop,152);
            SetCeilingTrigger(152,-1,Thing_Destroy,TID#,0,0);
        }
    }
}

 

Edited by Arsinikk

Share this post


Link to post

Ok, technically I came up with a really sloppy solution for the example script...

Script "MAP14_Coop_Fix" OPEN {
    int mapLump = GetLevelInfo (LEVELINFO_LEVELNUM);
    if(mapLump == 14){
	If (GameType() != GAME_SINGLE_PLAYER) {
		Spawn("TeleportDest", 2064.0, 3032.0, 0, 0, 180);
		Spawn("TeleportDest", 1312.0, 2200.0, 0, 0, 0);
		SpawnForced("TeleportDest", -1428.0, 1676.0, 0, 0, 64);
		SetCeilingTrigger(152,-1,Ceiling_CrushStop,152);
		SetCeilingTrigger(152,-1,Thing_Destroy,T_NONE,0,152);
		SetCeilingTrigger(152,-1,Thing_Destroy,T_NONE,0,96);
		SetCeilingTrigger(152,-1,Thing_Destroy,T_NONE,0,184);
		}
	}
}

 

 

But my question still stands, regarding how can I assign TIDs to already existing things in a "Doom Format" map via ACS?

Edited by Arsinikk

Share this post


Link to post

I don't think you can assign TIDs to monsters in sectors from ACS.

You could do that using ZScript (GZDoom only) by iterating though things in tagged sectors...

 

But in your case, if you just want to kill monsters in tagged sectors, you can use SectorDamage (or your solution that you posted while I was writing this, should work aswell).

Share this post


Link to post

Be sure to state you're targeting ZDaemon in the opening post -- that drastically changes the answer to the question.

 

Either way, you're far off the map in "here be dragons" territory. You're best off modifying the maps themselves -- distributing "fix patch" wads is pretty common for ZDaemon.

Share this post


Link to post

For ZDaemon, this can be done with the PATCHINF lump. But I don't think any other source port supports it. If you need the same in GZDoom, you can of course duplicate it with ZScript, using what Boris linked. Not sure about other ports like Zandronum though.

Example:

// Example PATCHINF lump for ZDaemon

[MAP03]
// Give thing number 12 the TID 666.
ThingH_ById_Change (12, *, *, *, 666)

 

Share this post


Link to post

In Doom Format, unlike UDMF, you cannot give a TID to a monster in the usual way. For this you need to use the Boom editing utility CLED, a CMD based 16-bit tool (so use of dosbox required) or SLADE. Your regular map editor may or may not respect your monster's new flag.

Share this post


Link to post
47 minutes ago, Xaser said:

Be sure to state you're targeting ZDaemon in the opening post -- that drastically changes the answer to the question.

I mean in the OP I specified that I was looking for an ACS-only solution. (Tho if you did look at a later post, I forgot to specify ACS-only again, so my bad).

 

So I was looking to see if I could somehow add TIDs to actors via ACS.

 

I know that in the patch that I worked with Worst on for Hell Revealations MAP30, we were able to use SecActEnter in order to set TIDs to actors that teleported into a sector. Problem is that it's specific to actors entering a sector rather than already being in a sector at the start of the map.

Edited by Arsinikk

Share this post


Link to post
15 minutes ago, Mordeth said:

In Doom Format, unlike UDMF, you cannot give a TID to a monster in the usual way. For this you need to use the Boom editing utility CLED, a CMD based 16-bit tool (so use of dosbox required) or SLADE. Your regular map editor may or may not respect your monster's new flag.

How is this supposed to work for giving them TIDs though? Even if you hack around with the THINGS lump, in vanilla Doom format, there is no field in there for the Thing id (tid), and you can't add new fields to the binary format, and expect source ports to still know how to parse it, unless you straight up convert the map to Hexen format.

Share this post


Link to post
10 minutes ago, Worst said:

How is this supposed to work for giving them TIDs though? Even if you hack around with the THINGS lump, in vanilla Doom format, there is no field in there for the Thing id (tid), and you can't add new fields to the binary format, and expect source ports to still know how to parse it, unless you straight up convert the map to Hexen format.

 

This dates all the way back to Boom. By messing with the thing's flags (normally used for skill settings and various options) you could 'extend' this to mean more stuff. Including giving a monster a TID.

 

Eg:

CLED ..\doom2\rmb\test.wad thing(4,134).FLAGS=9003

This gives for this wad thing with index 134 on map 4 a flag of 9003. This number acts as its recordnumber.. In EE, you can now use ExtraData to define the monster:

 

mapthing { recordnum 9003; type "FlayerDropOffSilent"; options "easy normal hard"; tid 9003 }

 

So the thing with recordnum 9003 becomes a monster type called FlayerDropOffSilent, which appears on all skill settings, and gets a TID of 9003.

Share this post


Link to post
28 minutes ago, Arsinikk said:

I mean in the OP I specified that I was looking for an ACS-only solution. (Tho if you did look at a later post, I forgot to specify ACS-only again, so my bad).

 

ZDaemon supports a different set of ACS features than GZDoom does (same for all "Z-family" ports), and GZDoom is by far the most common so the answers you get are most likely going to be GZDoom-centric, and may not work at all in anything else. Plus, ACS may not even be the right tool for the job -- Boris's post is right on the nose for GZDoom, and the PATCHINF lump that Worst linked looks like the ticket for ZDaemon.

 

A good rule of thumb is: if you're using a "Z-family" feature (e.g. ACS) and targeting something other than GZDoom, be sure to list the target source port, else folks will assume GZDoom.

Share this post


Link to post
1 hour ago, Arsinikk said:

Ok, technically I came up with a really sloppy solution for the example script...


Script "MAP14_Coop_Fix" OPEN {
    int mapLump = GetLevelInfo (LEVELINFO_LEVELNUM);
    if(mapLump == 14){
	If (GameType() != GAME_SINGLE_PLAYER) {
		Spawn("TeleportDest", 2064.0, 3032.0, 0, 0, 180);
		Spawn("TeleportDest", 1312.0, 2200.0, 0, 0, 0);
		SpawnForced("TeleportDest", -1428.0, 1676.0, 0, 0, 64);
		SetCeilingTrigger(152,-1,Ceiling_CrushStop,152);
		SetCeilingTrigger(152,-1,Thing_Destroy,T_NONE,0,152);
		SetCeilingTrigger(152,-1,Thing_Destroy,T_NONE,0,96);
		SetCeilingTrigger(152,-1,Thing_Destroy,T_NONE,0,184);
		}
	}
}

  

 

But my question still stands, regarding how can I assign TIDs to already existing things in a "Doom Format" map via ACS?

Hmmm... seems like the only flaw with this setup is that it also kills other players if they are in the sectors where Thing_Destroy is called, which is not exactly favourable. If I can get every type of the monster (a custom monster that replaces CommanderKeen) in the map to get a TID, then I could specify to only Thing_Destroy that TID.

 

25 minutes ago, Xaser said:

 A good rule of thumb is: if you're using a "Z-family" feature (e.g. ACS) and targeting something other than GZDoom, be sure to list the target source port, else folks will assume GZDoom.

Fair enough, I did specify ZDoom ports, but it seems when I said that I meant to support ZDaemon, Zandronum, ZDoom v2.8.1, and GZDoom. So basically all ZDoom-based source ports. That's usually the beauty of doing ACS patches is that I can get the map supported on all ZDoom-based ports. It also is a great way to avoid having to have separate WAD patches to have to be created for specific ports, i.e. ZDaemon, if I can just support them in the WAD itself.

Share this post


Link to post

Well, SectorDamage ACS function does have flags to only hurt nonplayers. Just not sure if this function is supported in ZDaemon.

 

Also, according to ZDoom wiki, Thing_Destroy with TID 0 should only kill monsters, not players.

Edited by jaeden

Share this post


Link to post

The vast majority of the time when folks say "ZDoom", they mean "GZDoom" -- back when ZDoom-proper was still in development, the two ports were basically in lockstep on feature development (aside from GZDoom-specific hardware renderer things), so "ZDoom" and "GZDoom" were effectively synonyms when it comes to feature-support unless discussing something hardware-specific, and that's still pretty much the case today since it was the norm for so long. Using "ZDoom" to refer to the entire extended family, including Zandronum, ZDaemon and Odamex, is very unusual despite being technically correct to a degree. You'll have to explicitly name these in order for folks to understand what you're looking for.

 

Back to the matter at hand: my eyes skimmed over the word "vanilla" in the opening post, and in that case an ACS patch is certainly tackling the problem from the wrong end. If there's something in the map that works in vanilla but not in the "Z-family" ports, or if this is a multiplayer-specific issue, there's usually something that can be fixed up map-side as long as you're not doing something crazy on the level of, like, KDiKDIZD :P. It'll probably be less of a pain to post or describe the setup in the map (e.g. why does this crusher need special behavior? what's broken?) and approach a map-side solution, rather than trying to band-aid over it in a language that wasn't designed for this use case (ACS is a very poor fit here no matter how you slice it, unfortunately).

 

If the undesired behavior seems like it's a port issue, it's also worth bringing to the devs' attention so it can get fixed there instead. If ZDaemon is indeed the odd one out, its most recent release was two days ago -- development is plenty active there.

Share this post


Link to post
52 minutes ago, jaeden said:

Well, SectorDamage ACS function does have flags to only hurt nonplayers. Just not sure if this function is supported in ZDaemon.

  

Also, according to ZDoom wiki, Thing_Destroy with TID 0 should only kill monsters, not players.

This actually did work in GZDoom...

 

ZDaemon however:

PCD_SectorDamage is not implemented yet

R.I.P.

 

 

Edit: Interestingly enough the Thing_Destroy doesn't work the same in ZDaemon as in GZDoom. I'm not sure if it doesn't read the sector argument, but it actually ends up killing all the monsters in the map. When in GZDoom it only kills everything in sector 152.

SetCeilingTrigger(152,-1,Thing_Destroy,T_NONE,0,152);
Edited by Arsinikk

Share this post


Link to post
56 minutes ago, Xaser said:

If there's something in the map that works in vanilla but not in the "Z-family" ports, or if this is a multiplayer-specific issue, there's usually something that can be fixed up map-side as long as you're not doing something crazy on the level of, like, KDiKDIZD :P. It'll probably be less of a pain to post or describe the setup in the map (e.g. why does this crusher need special behavior? what's broken?) and approach a map-side solution, rather than trying to band-aid over it in a language that wasn't designed for this use case (ACS is a very poor fit here no matter how you slice it, unfortunately).

Ok, fine I'll try and explain the setup.

 

In MAP14 of Hell Revealations, there is an optional minigame of sorts that is basically a race against time. The player teleports into a dark maze with a bunch of pacifist enemies. When the player enters it starts a timer (crusher) of around 4 minutes. Once 4 minutes is up the entire dark maze becomes a crusher that crushes you and the enemies.

 

The goal is to go through the maze and hit 3 switches which open up the exit to the maze. If the player enters the exit, they teleport back to the teleport they took to get there, and the crusher starts immediately to kill the monsters in the maze. In Vanilla, the teleport to the maze is W1, as the mapper didn't think it made much sense for multiplayer.

 

The problem is that TNS and other multiplayer groups want full access to the area anyway. So my solution was to add a teleport destination via ACS to give WR access to the maze. I completely disabled the crushers via ACS and kept the challenge of the section to instead be navigating through the ~100 enemies. IMO, having a crusher in multiplayer doesn't make much sense, however the minigame can still work well. My main problem is finding a way to kill all the monsters in the maze after a player has left the maze.

 

"SectorDamage" won't work for ZDaemon, since it doesn't support it. It also seems that "Thing_Destroy" won't work either since the ZDaemon iteration of it doesn't support the tag argument. I think the only way to be able to create a "killswitch" of sorts that won't hurt any players, is for me to find some way to assign TIDs to the maze monsters via ACS.

Share this post


Link to post

Try "disabling" the crusher section with a walkover line (or two or three) that's only reachable by Player 2 -- e.g. put the Player 2 start somewhere that Player 1 can't reach, like a high ledge in the start room, then have the trigger up there.

 

Since the crusher is reached via teleporter, I'd probably make the disable-trigger "swap" it with a second teleporter (e.g. close one teleport sector and open another) that uses a WR action and skips the crusher-activate linedefs. May need to scoot around some map geometry to fit, but it all oughta work without needing to reach for anything port-specific.

 

For BTSX et.al, I've gotten a lot of mileage out of this sort of "player 2 activates a co-op shortcut" trick. Beyond just fixing up stuff to work better in MP, it's a great way to let respawning players jump back into the fray in a large map, so it's a useful thing to have in the toolbelt.

 

 

[EDIT] Here's a gordian-knot-cut that may be easier in practice, provided you're fine maintaining two versions of the map: make a copy of the map called MAP14B or somesuch, convert it to Doom-in-Hexen (no UDMF support for ZDaemon IIRC), then use MAPINFO to go to that map instead of MAP14. From there, you've got full control over TIDs in the map, so you can script up the co-op fix without the trouble.

 

IMO it's not as ideal as going the vanilla route, since you'll have to maintain two maps in case of any future bugfixes or edits, but it's worth a try if the other way gets too tricky in practice. It'll allow the fix to live within the same wad, rather than a separate patch, so that's one base covered.

Edited by Xaser

Share this post


Link to post

I think I may have thought of a solution... Though I'm not exactly sure how to do it myself.

 

Worst and me were talking about in another map using "SecActHitFloor" to assign TIDs. Basically raising a sector and then dropping it forcing monsters to hit the floor and then assigning a TID that way. In the other map we were talking about it wouldn't work cuz the monsters were spawned stuck together, but in this map it'd probably work.

Share this post


Link to post

*I'm not familiar with using ACS in a non-UDMF format.

I did have a few ideas:

ThingCountNameSector returns the number of actors in a given sector by class. I believe this can be used to then create a for loop to assign the monsters a new TID and or kill them.

ThingCountNameSector wiki page.

 

This wiki page describes a script to check to make sure that an activator is not a player prior to applying damaging/killing the activator.
Using the above you may be able to script set linedef actions that activate the script when a monster bumps/uses or walks over them.
In this instance it would only matter that the activator is not a player and still do the same intended work.

I'm not sure if the actions above are available in your intended implementation and hope you find a solution that works as intended!

Additional thoughts:

You may be able to cause a radius quake by spawning a map point and causing a localized damaging event at the monsters' location.
Spawning projectiles into the the area may also be an option.

Edited by DeetOpianSky
Added text.

Share this post


Link to post
28 minutes ago, DeetOpianSky said:

ThingCountNameSector returns the number of actors in a given sector by class. I believe this can be used to then create a for loop to assign the monsters a new TID and or kill them.

 ThingCountNameSector wiki page.

The minor problem with this is that the monster needs to be of a default class like T_IMP or T_BARON. This is custom enemy that replaces CommanderKeen and there is no class "T_KEEN". Without an existing class to pull from, I can't really assign TIDs in this way.

 

30 minutes ago, DeetOpianSky said:

This wiki page describes a script to check to make sure that an activator is not a player prior to applying damaging/killing the activator.
Using the above you may be able to script set linedef actions that activate the script when a monster bumps/uses or walks over them.
In this instance it would only matter that the activator is not a player and still do the same intended work.

Uh... I don't think this is that helpful as this just "Returns the TID of the actor". The current problem is that the actors I'd like to affect don't have TIDs in the first place, so returning non-existing TIDs isn't useful in this case.

Thanks for the ideas and suggestions tho :)

Share this post


Link to post
1 hour ago, Arsinikk said:

The minor problem with this is that the monster needs to be of a default class like T_IMP or T_BARON. This is custom enemy that replaces CommanderKeen and there is no class "T_KEEN". Without an existing class to pull from, I can't really assign TIDs in this way.

 

Uh... I don't think this is that helpful as this just "Returns the TID of the actor". The current problem is that the actors I'd like to affect don't have TIDs in the first place, so returning non-existing TIDs isn't useful in this case.

Thanks for the ideas and suggestions tho :)

Without additional info/example it would be difficult to visualize a solution for this.

You may be able to change the player TIDs to something different, then use one of the above (if not in my thoughts, the thread above) options.

It is possible to give the monsters spawn numbers via MAPINFO for instance.
As a MAPINFO solution was proposed above, I'm guessing that isn't an option for your project ?

Additional information about how ZDOOM handles spawn numbers/ed numbers/etc.
https://zdoom.org/wiki/MAPINFO/Spawn_number_definition
https://zdoom.org/wiki/Spawn_number

Further thought, it may be possible to give the monster a class by modifying Zdefs.acs which is the definer of the classes before they're altered/added to via MAPINFO.

I've seen forum posts where changes were made for Hexen, nothing specific to this topic.
https://forum.zdoom.org/viewtopic.php?t=14666&p=296607

Edited by DeetOpianSky
additional link/info.

Share this post


Link to post

Figured I'd mention the solution I've arrived at for this (with some help from Worst):

 

Now we decided to assign TIDs via a "new patch" method, because Zandronum doesn't seem to like the "old patch" ACS-only method. The "old patch" does work in both ZDaemon and (G)ZDoom though, so I thought I'd include it anyway.

 

 

 

 

//////// OLD (LESS EFFICIENT) PATCH ///////

 

This is how you would assign TIDs via ACS only:

#define TID_TEMP 6666
#define TID_STONEBARON 6969

Script "MAP14_Coop_Fix" OPEN {
    int mapLump = GetLevelInfo (LEVELINFO_LEVELNUM);
    if (mapLump == 14) {
        if (GameType() != GAME_SINGLE_PLAYER) {
            SpawnForced("SecActHitFloor",-1424.0,1960.0,0,TID_TEMP,0);
            SetThingSpecial(TID_TEMP, ACS_ExecuteAlways, 254);
            SetActorProperty(TID_TEMP, APROP_Ambush, TRUE); // 'Monsters can activate'
            Thing_ChangeTID(TID_TEMP, 0);

            Floor_RaiseInstant(152, 0, 24); // 24*8 = 192, this should match the sector height!
            Delay(1); // 1 tic delay is needed in GZDoom for this trick
            Floor_LowerInstant(152, 0, 24); // The height here should be a positive number, because a negative value on a "Lower" special will actually cause it to move upwards!
            Spawn("TeleportDest", 2064.0, 3032.0, 0, 93, 120);  // Spawn teleport destinations for ZDoom ports
            Spawn("TeleportDest", 1312.0, 2200.0, 0, 94, 0);    // "TeleportDest" must have tags for them to work in ZDaemon
            SpawnForced("TeleportDest", -1428.0, 1676.0, -120, 184, 64);
            SetCeilingTrigger(100,-1,Ceiling_CrushStop,100);  // Check if crusher starts and then disable them.
            SetCeilingTrigger(153,-1,Ceiling_CrushStop,153);
            SetCeilingTrigger(152,-1,Ceiling_CrushStop,152);
            SetCeilingTrigger(153,-1,Thing_Destroy,TID_STONEBARON,0); // When crusher starts, kill all stone barons
        }
    }
}

// Note: Using script number 254 to not clash with the MAP30 patch that uses 255.
Script 254 (void)
{
	// Ignore if not a monster
	if (!(ClassifyActor(0) & ACTOR_MONSTER)) Terminate;

	// If the monster has no tag, give it one.
	if (ActivatorTID() == 0)
		Thing_ChangeTID(0, TID_STONEBARON);
}

What this script does is that it spawns the "SecActHitFloor" thing that checks when a thing hits the floor. Then it raises the floor, pauses and than instantly lowers it, causing the monsters to fall and hit the floor. This then adds TIDs to the monsters.

 

 

 

//////// NEW (BETTER) PATCH ///////

 

We decided to do something much simpler for the patch though, and it required using multiple lumps for different ZDoom ports.

 

First for DECORATE-supported ZDoom ports, I added a TID to all StoneBaron things:

// Code is simplified
// NoDelay must be added or else Thing_ChangeTID() will not work.
// This only applies when Thing_ChangeTID() is called in the Spawn state.

ACTOR BaronOfStone replaces CommanderKeen
{
[...]
  States
  {
  Spawn:
    KEKW A 0 NoDelay Thing_ChangeTID(0, 6969)
    Goto Idle
  Idle:
    KEKW AB 10 A_Look
    Loop
[...]
  }
}

 

For ZDaemon, I used PATCHINF to add the TID to all stone barons in the map:

// Patchinfo for MAP14.

[map14]

// Type 72 StoneBaron
// Select all Stonebarons in map and add TID
// Tags 6969 - TID_STONEBARON added so that they can get killed via ACS
// ThingH_Change (type, flags, tag, special, x, y, z, new_angle, new_type, new_flags, new_tag, new_special, new_arg1, ..., new_arg5)
ThingH_Change (72, *, 0, *, *, *, *, *, *, *, 6969)

 

And then once the TIDs are added, just some minor ACS to actually enable the fix:

#define TID_STONEBARON 6969

Script "MAP14_Coop_Fix" OPEN {
    int mapLump = GetLevelInfo (LEVELINFO_LEVELNUM);
    if (mapLump == 14) {
        // Only add coop fixes when not playing in Singleplayer
            if (GameType() != GAME_SINGLE_PLAYER) {
                Thing_Remove(93); // Remove singleplayer teleport destinations for ZDaemon
                Thing_Remove(94);
                Spawn("TeleportDest", 2064.0, 3032.0, 0, 93, 128);  // Spawn teleport destinations for ZDoom ports
                Spawn("TeleportDest", 1312.0, 2200.0, 0, 94, 0);    // "TeleportDest" must have tags for them to work in ZDaemon
                SpawnForced("TeleportDest", -1428.0, 1676.0, -128.0, 184, 64);
                SetCeilingTrigger(100,-1,Ceiling_CrushStop,100); // Check if crusher starts and then disable them.
                SetCeilingTrigger(153,-1,Ceiling_CrushStop,153);
                SetCeilingTrigger(152,-1,Ceiling_CrushStop,152);
                SetCeilingTrigger(153,-1,Thing_Destroy,TID_STONEBARON,0); // When crusher starts, kill all stone barons
            }
	}
}

 

Just figured I'd post my findings / solutions for the problem at hand, in case someone else runs into a similar problem and comes across this thread.

 

I'm a little less crazy about adding the TID to every single stone baron via DECORATE, but I can't think of another way to assign TIDs only on a particular map (via Vanilla Doom 2 map format).

Edited by Arsinikk

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...