ZScript questions for a TC I am doing (weapons/respawn/etc.)

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
Dark Pulse
Posts: 66
Joined: Fri Nov 21, 2014 5:35 pm
Operating System Version (Optional): Windows 10
Graphics Processor: nVidia with Vulkan support

ZScript questions for a TC I am doing (weapons/respawn/etc.)

Post by Dark Pulse »

I've been working on-again, off-again, on a TC of another game into the GZDoom engine. It's quite a nice game, and one where I've even seen some sprite rips floating around here in this very forum. :)

I've got a fair bit of stuff set up - working on the maps, textures are all in, custom playerclass in, everything is good. But eventually, I will need to begin to add in the weapons/items into the game, and do some things that are fundamentally different from Doom, and this is where the problem lies.

In summary:
  • The player may hold up to 9 weapons, which may include multiple copies of the same weapon, each with their own individual ammo count. If the player's weapon inventory is empty, they are given a default weapon that has infinite ammo (but a poor rate of fire and weak, as one would expect). Upon a weapon reaching zero ammo, a weapon actually stays in the player's inventory until it is shot again; no shot is produced but the weapon is then removed. A rare power-up can refill all ammo for all guns in the player's inventory to full.
  • The player may hold up to 9 keys, which may be of three colors, can be multiple copies of the same color, and like weapons, carry over from level to level. Each color-coded door uses up one key and stays open permanently once activated, while the used key is then removed from the key inventory. Naturally, you need the right color key to open the right color door.
  • A lives system (which theoretically should be easy enough to implement). Game over if all lives are gone, naturally.
  • A bonus system if the player doesn't get shot while killing 3+ enemies (ditto).
  • Upon killing the level boss, the player must return to the start room within a time limit, or be instantly killed, lose a life, and respawn the level boss, which must be killed again to restart the countdown.
  • One obstacle in the level, a mine, prints up a message (along with damaging the player) if they come into contact with it.
  • Possibly the trickiest thing I can think of besides the weapons - while player spawns at a spawn point, throughout the level are objects the player can choose to blow up. If the player blows one of them up, then dies, the player respawns next to this object, with the game remembering the last one the player shot and destroyed. I have no clue how I'd make something like this work.
I know my way around in a loose sense; here's where I currently stand on each of those points.
  • While I do know how to code up weapons (indeed, the default weapon is already in, if slightly imperfect due to having a faster rate of fire when tapped than if held down), I have no idea how to make it so that a second copy of the weapon can be picked up with its own individual ammo counter. The only workaround I can think of is that it remains one gun but just gets extra ammo, along with incrementing a variable so that if "9" guns have been picked up without dropping below a threshold (which would remove "1" gun), the game disallows it. But this is a huge kludge and not at all close to how the OG game did it.
  • Ditto for the keys - though this is theoretically much simpler. The bigger challenge would be how to enable multiple keys to be picked up, and of course, using the right key upon using the door line.
  • Lives aren't implemented at all. Shouldn't be too hard to implement though in theory, would just need to make sure you get dumped out to "demo reel."
  • Bonuses aren't implemented at all, but the way this works is pretty simple - if you kill an enemy, it goes up one. If you manage to kill three enemies without taking a hit, you get a bonus (which changes depending on how many you've gotten - can be anything from weapons, to powerups, to extra lives). If you get shot, decrement this counter by one unless it's zero.
  • Level boss system isn't set up yet, but I think the cheap and quick way to do it (besides, of course, running the countdown timer, changing the texture effects - it pretends to be flashing red/blue lighting, etc.) would be basically to just be able to mark a target sector (i.e; the starting sector) as the one to return to in order to stop the countdown and move the player to the tally screen. How to do that, I'm a bit less sure.
  • The mine is in-game and properly functions (though I do have to figure out how to make the explosion do damage rolloff), but I can't get it to print the message to the player for triggering it - possibly I've messed up code somewhere, but I can't see where.
  • I have no idea how to implement a moving respawn point at all - or indeed, if it even is possible.
I know this is quite a bit of stuff to dump down, but if anyone has answers to any of this stuff, I'd appreciate it. If need be, I can provide my code.

Oh yes, the OG game was up to two players, so ideally, I'd like solutions that would be netgame compatible if possible. (In practice, I'd like to expand this limit, but for now I will stick with trying to get the game as close as possible on GZDoom before I attempt to extend it.)
User avatar
RKD
Posts: 148
Joined: Sat Mar 19, 2022 9:52 am
Location: Argentina

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by RKD »

The key thingy should be easy to do using Puzzle Items. This is used by Hexen puzzles, where you need an item that is held in player's inventory once picked up (in your case, your "keys") and it's automatically taken away once used, either via ACS scripts or activating an action special in a linedef (in your case, each respective "door").

For the lives system, perhaps you could look for other mods that have that, to check their code or ask the modder perhaps.
User avatar
Virathas
Posts: 254
Joined: Thu Aug 10, 2017 9:38 am

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Virathas »

Weapons should be also fairly simple - since i am not sure what you try to emulate. But the main gist of the weapons, is to have ammo inside the actual weapon variable. As a simple example item:
(Untested)

Code: Select all

class MultiWeapon : Weapon
{
int ActualAmmo; // To keep Ammo somewhere, we can't really keep in a separate item
	override bool HandlePickup (Inventory item) // This allows having multiple copies of the same item
	{
		return false;
	}
}
For Lives system, i believe the Stronghold by Tormentor has some functionality like that, and it does support multiplayer.

I am not sure if you can directly move a respawn point, but utilizing Event Handlers (EventHandler.PlayerRespawned()) you could simply instantly warp the player to the desired location.
Jarewill
 
 
Posts: 1855
Joined: Sun Jul 21, 2019 8:54 am

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Jarewill »

For the weapons that depends on how exactly do you want it to be done.
Do you want each weapon to be on a specified slot and simply let players keep extra copies. (Such as the player carrying two of the same gun on slot 5)
Or that each newly picked up weapon gets added to the next slot, so weapon A will go on slot 1, weapon B on slot 2, a copy of weapon A on slot 3 and so on.
If it's the former, you can maybe find some use in this mod, even if it's not done well, it could still be a good base to use.

If it's the latter, I would do it by creating a base weapon class with states of all planned weapons and then create copies of it for each of the 9 slots.
Then inside the player class I would create a dynamic array where I would store weapon information.
Each weapon pickup would then append weapon information to the end of the array in its TryPickup override.
Finally in each actual weapon I would check for the array information and use specific states based on it, so if a slot 1 weapon would check the array's first variable (number 0) and it would read "shotgun", then it would use "shotgun"-specific states.
And as Virathas said, if you want each individual weapon to keep track of the information, you should keep it in variables inside the weapon.

I don't think the respawn points will be that hard, and since it's planned to be a TC, using ACS for this might be better as it will be map-specific.
You can place a teleport destination next to the object to blow up and apply a script to said object that records a global variable when it's destroyed.
Then using a RESPAWN script, use a Teleport(_NoFog) function to teleport the player to a specific destination based on the variable.
So if you break object A, which will record the teleport destination's TID next to it and then teleport the player in the script to that specific teleport destination, it should work.

Sadly these next 2 weeks I will be unable to access my computer much, so I can't provide any working code, but I hope at least I gave you some ideas to execute it.
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Sir Robin »

Note: I haven't tried these things, I'm just throwing out ideas:

For the keys, you could also do it with an [wiki=Events_and_handlers]event handler[/wiki] WorldLineActivated() trap. See if the line has a key requirement and if so deduct it from the activator.

For the kills bonus you could do it with an event handler WorldThingDied() trap to track kills back to their killer and keep a tally. To reset the bonus you could watch damage from the player pawn or use an event handler WorldThingDamaged() trap to watch for damage on the player.

For the boss-timer-return thing, you could do it with event handlers, or more simply / visually with an invisible actor to manage it. Create an actor that when activated starts a count down and when done it does the player death / boss respawn / player live lost / etc. When this actor is deactivated it stops and resets the timer. Put that actor in your map with a unique TID and set the boss monster to activate that TID when dead. Set linedef(s) in the "safe zone" to deactivate that TID when crossed.

For the mine, you just can't get a message to display? What are you using? Try console.printf("You stepped on a mine, dumbass!");

For the respawn point, use an event handler WorldThingDied() trap to watch for destruction of said object. Then perhaps you can move the player start spot actor to that location? If not, store the location and then wait for a PlayerRespawned() event and move the spawning player to that stored location.
User avatar
Dark Pulse
Posts: 66
Joined: Fri Nov 21, 2014 5:35 pm
Operating System Version (Optional): Windows 10
Graphics Processor: nVidia with Vulkan support

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Dark Pulse »

Apologies in advance for this postwall, but I'm replying to like four people!
RKD wrote:The key thingy should be easy to do using Puzzle Items. This is used by Hexen puzzles, where you need an item that is held in player's inventory once picked up (in your case, your "keys") and it's automatically taken away once used, either via ACS scripts or activating an action special in a linedef (in your case, each respective "door").
That does indeed look like it might do the bill quite nicely, and I tried searching for something like that (honest!), so I'm surprised I never quite found it.

It looks like I will have to update my GZDoom (and thus the mod) due to the PuzzleItem.FailSound property only being introduced in 4.8.1 (the original game did play a different sound if you did not have the right key), but that really isn't going to be an issue.
Virathas wrote:Weapons should be also fairly simple - since i am not sure what you try to emulate.
Well, more than that. I'll try to explain it a little more clearly.

The original game gives the player an array of up to nine weapons; there are ten possible weapon pickups, plus an eleventh default weapon if the player has absolutely no weapons, and is not allowed to be selected if you have literally anything better. Each weapon can be of any type (except the default weapon) - including multiple copies of the same weapon. To use an example, one is a weak, rapid-firing machinegun type, but it's entirely possible to have one, two, or even nine of those - all of which keep track of their own individual ammo. This is what is throwing me off, since obviously, Doom absolutely does not work like this - you can't hold two Shotguns with their own individual ammo.

The amount of ammo does vary from weapon to weapon, but never does it vary from type to type. To keep using that example, the rapid-fire machinegun has an ammo capacity of 35 shots, and they all will for each one you pick up, compared to, say, a grenade launcher which will only have 6. So if you've used your machinegun down to 15 bullets, and pick up a new one, you now have two of them - the one you were using with 15, and the fresh new one you picked up with 35. Add in two grenade launchers and you've got one machinegun with 15 shots, one with 35, and the grenade launchers both have 6, and you've used four of your nine weapon slots.

Upon reaching zero ammo, a weapon is not technically removed. It's only when you attempt to fire a weapon with zero ammo that it gets removed; this is because some levels have an ammo recharger powerup, and picking that up will restore all the ammo for every gun you have to full.

Does that make it clearer?
Jarewill wrote:For the weapons that depends on how exactly do you want it to be done.

I don't think the respawn points will be that hard, and since it's planned to be a TC, using ACS for this might be better as it will be map-specific.
You can place a teleport destination next to the object to blow up and apply a script to said object that records a global variable when it's destroyed.
Then using a RESPAWN script, use a Teleport(_NoFog) function to teleport the player to a specific destination based on the variable.
So if you break object A, which will record the teleport destination's TID next to it and then teleport the player in the script to that specific teleport destination, it should work.

Sadly these next 2 weeks I will be unable to access my computer much, so I can't provide any working code, but I hope at least I gave you some ideas to execute it.
I commented clearer on how it works above; I don't know if that helps.

The respawn issue... yeah, technically I could do that, I guess, since yes, it'd obviously be map-specific. I'd just need to keep track of which one was the last one blown up, though, and use that to activate a respawn point near it. Though admittedly, that'd be essentially down to putting a teleport destination next to every single barrel, and that can be a bit much, as levels can have dozens of barrels.

While I'll do that if I got to ("Works" is better than nothing, after all), I'd prefer something a bit cleaner if possible.
Sir Robin wrote:For the boss-timer-return thing, you could do it with event handlers, or more simply / visually with an invisible actor to manage it. Create an actor that when activated starts a count down and when done it does the player death / boss respawn / player live lost / etc. When this actor is deactivated it stops and resets the timer. Put that actor in your map with a unique TID and set the boss monster to activate that TID when dead. Set linedef(s) in the "safe zone" to deactivate that TID when crossed.

For the mine, you just can't get a message to display? What are you using? Try console.printf("You stepped on a mine, dumbass!");

For the respawn point, use an event handler WorldThingDied() trap to watch for destruction of said object. Then perhaps you can move the player start spot actor to that location? If not, store the location and then wait for a PlayerRespawned() event and move the spawning player to that stored location.
That boss idea is actually pretty clever. I might go with that. The only catch is that the level timer does vary per-level; generally speaking, earlier levels give you more time, while harder levels give you a much narrower time limit (and in a few maps' cases, were all but impossible to achieve without knowing of some secret passages). Admittedly not helped is that the game had classic early-90s control schemes, so you could turn left/right, but strafing was somewhat unintuitive (though possible). These limits would be adjusted for this GZDoom recreation for obvious reasons, but yeah, I do need it to be settable on a per-level basis.

For the mine, my code currently looks like this. It's probably a shambles and wrong, and obviously there's some commented-out crap from past experiments that I'm not quite sure are useful to delete yet, so they're hanging on for now.

Code: Select all

class Mine : Actor
{
		Default
		{
				Health 1;
				Radius 12;
				Height 27;
				+SOLID // For collision...
				+TOUCHY // To die immediately on being touched...
				+SHOOTABLE // Needed to actually be damaged...
				+NONSHOOTABLE // ...but to not actually hittable by projectiles, we need this too. WTF?
				+BUMPSPECIAL // Player/Monster touching it activates it
				// Activation THINGSPEC_ThingActs; // Sets it so the MINE is killed, not the player
				Activation THINGSPEC_ThingTargets; // Sets it so the MINE is killed, not the player
		}
		States
		{
		Spawn:
				MINE A 47; // NoDelay Thing_SetSpecial(0, 73, 0, 22, 0);
				// Damage (73) self (first 0) with guaranteed death (second 0)
				// of a melee hit (22); last 0 isn't used
				MINE B 39;
				Loop;
		Death:
				TNT1 A 1 A_Print("Mine!");
				TNT1 A 0 A_SpawnItemEx("Explosion");
				Stop;
		}
}
The basic gist: The mine flips between two frames of animation. If it is touched (by a player or an enemy), it explodes on contact, dealing damage in a falloff radius (that is covered by the Explosion actor, which I've not pasted here), and if it's the player, prints "Mine!" onto the screen in a big font (which I've admittedly yet to define). It can also be shot by some weapons, which naturally will explode it from a safe distance and with no such message.

I think what I'm screwing up is that the mine considers itself the killer, rather than whoever touched it, so it's trying to print it to the mine's view, which obviously doesn't make sense. But I'm not quite sure what to do to fix that.

For the respawn point... that's actually super clever. So you're saying I can basically have a script running that would move the player spawn points depending on who blew up the barrel? That'd be a very elegant solution, and I didn't even think something like that was possible (I thought all map-placed Things were essentially static - so you could spawn new stuff, or change properties of existing stuff, but not move them entirely). That'd probably be what I'd go with, if that's possible.
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Sir Robin »

Dark Pulse wrote:That boss idea is actually pretty clever. I might go with that. The only catch is that the level timer does vary per-level; generally speaking, earlier levels give you more time, while harder levels give you a much narrower time limit (and in a few maps' cases, were all but impossible to achieve without knowing of some secret passages). Admittedly not helped is that the game had classic early-90s control schemes, so you could turn left/right, but strafing was somewhat unintuitive (though possible). These limits would be adjusted for this GZDoom recreation for obvious reasons, but yeah, I do need it to be settable on a per-level basis.
Since the actor is just a dummy and you never see it, the visual attributes aren't important and you can re-use them for your own purposes. For example, angle will never be used for anything. So in your Activate() function, check angle, multiply it by 35, then set the timer to that value. Now you can place the actor on the map and set the angle to the number of seconds for the timer and it just works. If you don't like that idea then pick another variable or add a [wiki=User_variable]user variable[/wiki]. Now you can even add multiple copies of that actor to the map set to different times and set each one to appear for a different difficulty level, so higher difficulty will have less time or whatever.

For the mine, sounds like it's not that you can't display the message, it's that you're having trouble with the detonation logic and determining the triggering factor. Is that right?
Maybe it would help if you decided exactly what you want the mine to react to - players, monsters, projectiles, hitscans, walls, ceilings, floors, polyobjects, etc.
I think for a basic mine you could just set it to special and shootable. Then the touch function will tell you who bumped it and the takedamage function will tell you who shot it.
See my touch trigger for a very basic example of that.
Dark Pulse wrote:For the respawn point... that's actually super clever. So you're saying I can basically have a script running that would move the player spawn points depending on who blew up the barrel? That'd be a very elegant solution, and I didn't even think something like that was possible (I thought all map-placed Things were essentially static - so you could spawn new stuff, or change properties of existing stuff, but not move them entirely). That'd probably be what I'd go with, if that's possible.
I'm not sure if it's possible, I haven't tried that before. I'm just throwing ideas out for you to explore. First the to do is answer that question - can you move player start? Create a quick map to test that out.
User avatar
Dark Pulse
Posts: 66
Joined: Fri Nov 21, 2014 5:35 pm
Operating System Version (Optional): Windows 10
Graphics Processor: nVidia with Vulkan support

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Dark Pulse »

Sir Robin wrote:Since the actor is just a dummy and you never see it, the visual attributes aren't important and you can re-use them for your own purposes. For example, angle will never be used for anything. So in your Activate() function, check angle, multiply it by 35, then set the timer to that value. Now you can place the actor on the map and set the angle to the number of seconds for the timer and it just works. If you don't like that idea then pick another variable or add a [wiki=User_variable]user variable[/wiki]. Now you can even add multiple copies of that actor to the map set to different times and set each one to appear for a different difficulty level, so higher difficulty will have less time or whatever.
Yeah, right now I got a couple of private arrays set up in the player class (to try to track the weapons and keys), but other than declaring the arrays, right now they're totally unused, since I was trying to figure out solutions to this problem, so they might not even stay.

That does sound like a good way to do this though, yeah. I know about invisible actors and stuff, and there will definitely never be a time where I will need more than 359*35 for the timer, so I think this will actually be a pretty neat idea. (Although admittedly I'd probably prefer a user variable, because I am accounting for the possibility of other players potentially making maps, and doing an "expanded version" of the TC down the line where I purposely break the limitations of the normal game - but for this "as close as possible to the original" one, I am keeping it close to what the game did.)
Sir Robin wrote:For the mine, sounds like it's not that you can't display the message, it's that you're having trouble with the detonation logic and determining the triggering factor. Is that right?
Maybe it would help if you decided exactly what you want the mine to react to - players, monsters, projectiles, hitscans, walls, ceilings, floors, polyobjects, etc.
I think for a basic mine you could just set it to special and shootable. Then the touch function will tell you who bumped it and the takedamage function will tell you who shot it.
See my touch trigger for a very basic example of that.
Well, as I said above, for the initial version of the mod I am planning on keeping it as close to the source material as possible. The original game did not have any sort of hitscan weaponry, it didn't have polyobjects, it didn't have lifts, it didn't even have height variation. Hell, it doesn't even have a textured floor or ceiling!

Exactly three (possibly four) things could trigger a mine:
  • Player touches mine. This is the only one that prints the "Mine!" message - notably, for both players if it's a two-player game, IIRC. (This is something I'd want to fix for this though.)
  • Monster touches mine. The monster is dealt damage by the explosion; most weaker ones die immediately. No message is printed.
  • The player shoots the mine. Only certain weapons can do this, IIRC, but in this case, the shot blows the mine up; player takes damage if too close. No message is printed. (Not sure if it would if the player took explosion damage versus touching - would have to test that.)
  • Theoretical: Monster shoots the mine. I've never seen this, though, so I'm not sure if it's possible.
Eventually I would probably want to expand this, but again, that would wait for the extended edition.

Sir Robin wrote:I'm not sure if it's possible, I haven't tried that before. I'm just throwing ideas out for you to explore. First the to do is answer that question - can you move player start? Create a quick map to test that out.
I'll have to look into that, since I'm not sure myself.
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Sir Robin »

Dark Pulse wrote:there will definitely never be a time where I will need more than 359*35 for the timer, so I think this will actually be a pretty neat idea. (Although admittedly I'd probably prefer a user variable, because I am accounting for the possibility of other players potentially making maps, and doing an "expanded version" of the TC down the line where I purposely break the limitations of the normal game - but for this "as close as possible to the original" one, I am keeping it close to what the game did.)
UDMF spec says that angle is an integer of no less than 32 bits. GZDoom reads it from the map and stores it as a double on the actor. So there is plenty of value range there. Does the value get clamped/modded to 360 when the map is loaded? Test it and find out.
Dark Pulse wrote:Exactly three (possibly four) things could trigger a mine:
So essentially one thing can trigger a mine: an actor. Players, projectiles, and monsters are all actors. So only thing you need to look for is actor collisions with your mine. Then determine what type of actor it was. If it's a player, display your message. If it's a projectile, trace it back to it's owner and if the owner is a player display your message. If it's owner is a monster and you decide you don't want monsters to shoot mines, then abort the collision.
You also need to think about other actors you want to filter out. For example do you have moving platforms or decorations that could come into contact with a mine? If the player kills a monster right near a mine and the monster drops loot or spurts blood and that touches the mine, should that trigger it?
Think of all the possibilities and then decide what you want to do in each case, then write your code to do that.
User avatar
Virathas
Posts: 254
Joined: Thu Aug 10, 2017 9:38 am

Re: ZScript questions for a TC I am doing (weapons/respawn/e

Post by Virathas »

The original game gives the player an array of up to nine weapons; there are ten possible weapon pickups, plus an eleventh default weapon if the player has absolutely no weapons, and is not allowed to be selected if you have literally anything better. Each weapon can be of any type (except the default weapon) - including multiple copies of the same weapon. To use an example, one is a weak, rapid-firing machinegun type, but it's entirely possible to have one, two, or even nine of those - all of which keep track of their own individual ammo. This is what is throwing me off, since obviously, Doom absolutely does not work like this - you can't hold two Shotguns with their own individual ammo.

The amount of ammo does vary from weapon to weapon, but never does it vary from type to type. To keep using that example, the rapid-fire machinegun has an ammo capacity of 35 shots, and they all will for each one you pick up, compared to, say, a grenade launcher which will only have 6. So if you've used your machinegun down to 15 bullets, and pick up a new one, you now have two of them - the one you were using with 15, and the fresh new one you picked up with 35. Add in two grenade launchers and you've got one machinegun with 15 shots, one with 35, and the grenade launchers both have 6, and you've used four of your nine weapon slots.

Upon reaching zero ammo, a weapon is not technically removed. It's only when you attempt to fire a weapon with zero ammo that it gets removed; this is because some levels have an ammo recharger powerup, and picking that up will restore all the ammo for every gun you have to full.

Does that make it clearer?
There is one small bit that is still missing - how do you go around changing weapons? For example, if the first two weapons you find are shotguns, do they occupy slot 2 and slot 3? Or you can access both on slot 3?
Post Reply

Return to “Scripting”