Preserving inventory over a death exit

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
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

Preserving inventory over a death exit

Post by argv »

Is there some way now to preserve inventory items across a death exit?

I found this old thread saying the only way to do that is with (ACS) global variables. Now that we have ZScript, is that still the case, or is there now some better way to deal with it?
User avatar
Graf Zahl
Lead GZDoom+Raze Developer
Lead GZDoom+Raze Developer
Posts: 49231
Joined: Sat Jul 19, 2003 10:19 am
Location: Germany

Re: Preserving inventory over a death exit

Post by Graf Zahl »

You have to store the inventory somewhere safe, because upon death it gets destroyed. With ZScript it should be doable, if you override the player's Die method where you move the inventory to a safe place and PostBeginPlay where you move it back to the new player.

However, this won't work if the game restarts on death. In that case all game related data will be deleted.
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

Re: Preserving inventory over a death exit

Post by argv »

Restarts on death, as in, the map changes while the player is dead?

I'm referring to the trick used by some maps where you're teleported into a room full of barrels and a BossBrain. You telefrag a barrel, the barrels explode, you die, the BossBrain dies, and the map ends while you're dead, forcing a pistol start on the next map.
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

Re: Preserving inventory over a death exit

Post by argv »

I found I can transfer a dead player's inventory to a temporary actor using an event handler, but when the map changes, the temporary actor doesn't exist any more!
User avatar
Graf Zahl
Lead GZDoom+Raze Developer
Lead GZDoom+Raze Developer
Posts: 49231
Joined: Sat Jul 19, 2003 10:19 am
Location: Germany

Re: Preserving inventory over a death exit

Post by Graf Zahl »

You have to transfer it to some static object that can survive a map change, i.e. a thinker stored in the STAT_STATIC slot.
argv
Posts: 184
Joined: Tue Aug 30, 2016 4:47 pm

Re: Preserving inventory over a death exit

Post by argv »

Well, that was a rather ugly solution. Here's what I came up with:

Code: Select all

class DeathExitInventoryHolder : Actor {
	default {
		+NOINTERACTION;
	}
	
	int PlayerNumber;
	
	override void BeginPlay() {
		Super.BeginPlay();
		ChangeStatNum(STAT_STATIC);
		Console.Printf("DeathExitInventoryHolder for player %d spawned!", PlayerNumber);
	}
	
	override void OnDestroy() {
		Console.Printf("\cgInventoryHolder for player %d destroyed!", PlayerNumber);
	}
	
	static DeathExitInventoryHolder Find(int playerNumber) {
		let i = ThinkerIterator.Create("DeathExitInventoryHolder", statnum: STAT_STATIC);
		DeathExitInventoryHolder h;
		
		while (h = DeathExitInventoryHolder(i.Next()))
		if (h.PlayerNumber == playerNumber)
			return h;
		
		return null;
	}
	
	static DeathExitInventoryHolder Create(int playerNumber) {
		let h = Find(playerNumber);
		
		if (!h) {
			Console.Printf("No DeathExitInventoryHolder for player %d found. Creating one.", playerNumber);
			h = DeathExitInventoryHolder(Spawn("DeathExitInventoryHolder"));
			h.PlayerNumber = playerNumber;
		}
		else
			Console.Printf("\cgInventoryHolder.Create called for player %d, but one already exists!", playerNumber);
		
		return h;
	}
}

class DeathExitInterceptor : StaticEventHandler {
	override void WorldUnloaded(WorldEvent evt) {
		for (let pn = 0; pn < MAXPLAYERS; pn++) {
			if (!playeringame[pn])
				continue;
			
			let h = DeathExitInventoryHolder.Find(pn);
			if (!h) {
				Console.Printf("\cgNo DeathExitInventoryHolder for player %d!", pn);
				continue;
			}
			
			// Fully clear the inventory of the DeathExitInventoryHolder, including UNDROPPABLE items.
			h.ClearInventory();
			while (h.Inv)
				h.RemoveInventory(h.Inv);
			
			if (players[pn].playerstate == PST_DEAD) {
				let pc = players[pn].mo;
				
				if (pc.Inv) {
					Console.Printf("Player %d is dead at map exit. Saving inventory...", pn, players[pn].mo.Inv.GetClassName());
					
					Inventory inv;
					while (inv = pc.Inv) {
						Console.Printf("* %s", inv.GetClassName());
						pc.RemoveInventory(inv);
						if (inv.bUndroppable) {
							h.AddInventory(inv);
							inv.ChangeStatNum(Thinker.STAT_STATIC);
						}
					}
				}
				else
					Console.Printf("Player %d is dead, and has no inventory.", pn);
			}
		}
	}
	
	override void PlayerEntered(PlayerEvent evt) {
		let pn = evt.PlayerNumber;
		let pc = players[pn].mo;
		let h = DeathExitInventoryHolder.Find(pn);
		
		if (h) {
			if (h.Inv) {
				Console.Printf("Player %d entered. Restoring inventory.", pn);
				while (h.Inv) {
					let i = h.Inv;
					h.RemoveInventory(i);
					i.ChangeStatNum(Thinker.STAT_INVENTORY);
					
					let handled = false;
					for (let pi = pc.Inv; pi; pi = pi.Inv)
					if (pi.HandlePickup(i)) {
						handled = true;
						break;
					}
					
					if (!handled)
						pc.AddInventory(i);
				}
			}
			else
				Console.Printf("Player %d entered, but does not have any inventory waiting.", pn);
		}
		else {
			Console.Printf("Player %d entered, and is new. Allocating inventory holder.", pn);
			DeathExitInventoryHolder.Create(pn);
		}
	}
	
	override void PlayerDisconnected(PlayerEvent evt) {
		let pn = evt.PlayerNumber;
		
		Console.Printf("Player %d disconnected. Destroying inventory holder.", pn);
		let h = DeathExitInventoryHolder.Find(pn);
		if (h)
			h.Destroy();
	}
}
It works, mostly, but damn if it isn't verbose. It turns out that I also have to move the inventory items themselves into the STAT_STATIC slot. It also has the rather odd behavior that, if it preserves a weapon across a death exit, the weapon can no longer be selected using the weapon slot buttons or next/previous weapon buttons, only by the “use” command. (I only tested it with a weapon, so I have no idea what other strangeness could result from such a hack.)

I also came up with a much cleaner solution:

Code: Select all

class DeathExitInterceptor : StaticEventHandler {
	override void WorldUnloaded(WorldEvent evt) {
		for (let pn = 0; pn < MAXPLAYERS; pn++)
		if (playeringame[pn] && players[pn].playerstate == PST_DEAD) {
			Console.Printf("Player %d is dead at level exit. Resurrecting and resetting inventory.", pn);
			players[pn].Resurrect();
			let pc = players[pn].mo;
			pc.ClearInventory();
		}
	}
}
This, however, has an even bigger problem: ClearInventory also removes the PlayerPawn's start items, and APlayerPawn::GiveDefaultInventory is not exposed to ZScript! A feature request for that has already been posted.
Post Reply

Return to “Scripting”