// content by Luigifan

//----------- ability definition -----------

--actions
freeslot(
"S_PLAY_ANNIE_HEALING",
"S_PLAY_ANNIE_ACCELERATEDHEALING",
"S_PLAY_ANNIE_EMERGENCYCARRY",
"S_PLAY_ANNIE_REVIVE",
"S_PLAY_ANNIE_ACCELERATEDREVIVE"
)

--passive abilities
freeslot(
"S_PLAY_ANNIE_TOUGHBODY",
"S_PLAY_ANNIE_HARDTOKILL_0RINGS",
"S_PLAY_ANNIE_HARDTOKILL_DROWNING",
"S_PLAY_ANNIE_HARDTOKILL_CRUSH",
"S_PLAY_ANNIE_HARDTOKILL_INSTANTKILL",
"S_PLAY_ANNIE_HARDTOKILL_PHAZON",
"S_PLAY_ANNIE_HARDTOKILL_MINECARTCRASH"
)

--objects
freeslot("MT_PLAYERDOWNMARKER")

freeslot("SPR_PDWN")

freeslot(
"S_PLAYERDOWNMARKER_SPAWNSTATE",
"S_PLAYERDOWNMARKER_RAISESTATE",
"S_PLAYERDOWNMARKER_DEATHSTATE"
)

--sound effects
freeslot("sfx_heal")

// main thinker
addHook("ThinkFrame", function()
	for player in players.iterate do
		if player.mo.skin ~= "annie_belnades" then
			continue
		end
	end
end)

//Healing (Code to heal other players (increase Ring count or HP))
--Healing flags
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then --No skin check this time, this hook applies to everyone!
	
		if player.pfhealingflag == nil then
			player.pfhealingflag = 0
		end
		
		if player.pfhealingflag ~= 0 then
			if not (leveltime%(TICRATE*4)) then
				player.pfhealingflag = 0 --Reset the healing flag on a character who isn't being healed
			end
		end
		
		if player.pfhealingflag == 1
		or player.pfhealingflag == 2
		or player.pfhealingflag == 3
		or player.pfhealingflag == 4 then --For any version of Annie's healing
			if not (leveltime%TICRATE) then
				if player.powers[pw_super] > 0 then
					P_GivePlayerRings(player, 1) --Counteract Super ring drain
				end
				if (player.mo.subsector.sector.special == 9 and P_IsObjectOnGround(player.mo) == true) or player.mo.subsector.sector.special == 10 then
					P_GivePlayerRings(player, 2) --Counteract Ring drain sectors
				end
			end
		end
		
		if player.pfhealingflag == 1 then --For basic healing applied to a player other than Annie herself
			if not (leveltime%TICRATE) then
				if player.mo.energy ~= nil then --if the player being healed has an energy meter (i.e. is one of the Belnades sisters)
					player.mo.energy = $ + 3*FRACUNIT
				end
			end
		end
		
		if player.pfhealingflag == 2 then --For accelerated healing applied to a player other than Annie herself
			if not (leveltime%TICRATE) then
				if player.mo.energy ~= nil then --if the player being healed has an energy meter (i.e. is one of the Belnades sisters)
					player.mo.energy = $ + 6*FRACUNIT
				end
			end
		end
	end
end)

--Healing sound
sfxinfo[sfx_heal] = {
	singular = false,
	priority = 16,
	caption = "Healing",
	flags = SF_NOMULTIPLESOUND
}

--Healing hook
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then --check if the player object exists
    
        if player.mo.skin ~= "annie_belnades" then --is the player NOT playing as Annie?
            return --stop running hook for this player
        end
		
		/*if player.custom1down == nil then
			player.custom1down = 0
		end
		
		if player.cmd.buttons & BT_CUSTOM1 > 0 then
			player.custom1down = $1 + 1
		else
			player.custom1down = 0
		end*/
		
		if player.custom1down > 1 --if custom1 is being pressed
		and player.custom3down == 0 --and custom3 is not being pressed
		and player.mo.energy >= 5*FRACUNIT/2 then
			if not (leveltime%TICRATE) then
				if player.mo.energy >= 5*FRACUNIT/2 then
					player.mo.energy = $ - 5*FRACUNIT/2
					S_StartSoundAtVolume(player.mo, sfx_heal, 90, nil)
					if not (player.custom1down%(TICRATE*2)) then --Every 2 seconds
						if P_LookForPlayers(player.mo, 20*FRACUNIT, true, false) == true then --Is there another player within 20 fracunits of Annie?
							local target = player.mo.target.player --defining the player found in P_LookForPlayers as the target for the rest of the healing script
							if target.player and target.player.valid and target.player.playerstate ~= PST_DEAD and target.player.mo ~= player.mo then --Is there a live player OTHER THAN Annie herself within 20 fracunits of her?
								if G_CoopGametype() == true
								or (G_GametypeHasTeams() == true and player.ctfteam == target.player.ctfteam) then --Is that other player on the same team as Annie?
									if P_CheckSight(player.mo, target.player) == true then --Can Annie actually see the other player?
										if target.player.skin == ("basesamus" or "samus") then --Healing Samus Aran
											target.player.sam_energy = $ + 15
										end
										if target.player.skin == ("Mario" or "Luigi") then --Healing the Mario Bros.
											target.player.mariocoins = $ + 1
										end
										if target.player.skin == "sgimario" then --Healing N64 Mario
											target.player.rkyHealth = $ + 136
										end
										if target.player.skin == "lilac" then --Healing Lilac
											target.player.oldrings = $ + 5
										end
										if target.player.skin == "drone" then --Healing Drone and refilling its energy
											target.dvars.hull = $ + 2
											target.dvars.energy = $ + 25
										end
										if target.player.skin == "ass" then --restore Almost Super Sonic's depleted stats
											if target.player.normalspeed < skins[target.player.mo.skin].normalspeed then
												target.player.normalspeed = $ + 1
											end
											if target.player.jumpfactor < skins[target.player.mo.skin].jumpfactor then
												target.player.jumpfactor = $ + 1*FRACUNIT/100
											end
											if target.player.actionspd < skins[target.player.mo.skin].actionspd then
												target.player.actionspd = $ + 1
											end
											if target.player.mindash < skins[target.player.mo.skin].mindash then
												target.player.mindash = $ + 1
											end
											if target.player.maxdash < skins[target.player.mo.skin].maxdash then
												target.player.maxdash = $ + 1
											end
										end
										if target.player.skin == "satsr_sonic" then
											target.player.soulgauge = $ + 25
										end
										P_GivePlayerRings(target.player, 1) --Heal the other player
										target.player.pfhealingflag = 1 --Flag to tell the game that Annie is applying basic healing to a player other than herself
										end
									end
								end
							end
						else
							P_GivePlayerRings(player, 1) --If Annie can't find another player to heal, she heals herself
							player.pfhealingflag = 3 --Flag to tell the game that Annie is applying basic healing to herself
						end
				else
					return
				end
			end
		end
	end
end)

//Accelerated Healing (Code to double the speed of healing)
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		/*if player.custom1down == nil then
			player.custom1down = 0
		end 
		
		if player.cmd.buttons & BT_CUSTOM1 > 0 then
			player.custom1down = $1 + 1
		else
			player.custom1down = 0
		end*/
		
		if player.custom1down > 1 --if custom1 is currently being pressed
		and player.custom3down > 1 --and custom3 is also currently being pressed
		and player.custom1down > player.custom3down --and custom1 was pressed *before* custom3
		and player.mo.energy >= 5*FRACUNIT then --and the player has energy
			if not (leveltime%TICRATE) then
				if player.mo.energy >= 5*FRACUNIT then
					player.mo.energy = $ - 5*FRACUNIT
					S_StartSoundAtVolume(player.mo, sfx_heal, 120, nil)
					if not (player.custom1down%TICRATE) then --Every second
						if P_LookForPlayers(player.mo, 20*FRACUNIT, true, false) == true then --Is there another player within 20 fracunits of Annie?
							local target = player.mo.target.player --defining the player found in P_LookForPlayers as the target for the rest of the healing script
							if target.player and target.player.valid and target.player.playerstate ~= PST_DEAD and target.player.mo ~= player.mo then --Is there a live player OTHER THAN Annie herself within 20 fracunits of her?
								if G_CoopGametype() == true
								or (G_GametypeHasTeams() == true and player.ctfteam == target.player.ctfteam) then --Is that other player on the same team as Annie?
									if P_CheckSight(player.mo, target.player) == true then --Can Annie actually see the other player?
										if target.player.skin == ("basesamus" or "samus") then --Healing Samus Aran
											target.player.sam_energy = $ + 30
										end
										if target.player.skin == ("Mario" or "Luigi") then --Healing the Mario Bros.
											target.player.mariocoins = $ + 2
										end
										if target.player.skin == "sgimario" then --Healing N64 Mario
											target.player.rkyHealth = $ + 272
										end
										if target.player.skin == "lilac" then --Healing Lilac
											target.player.oldrings = $ + 10
										end
										if target.player.skin == "drone" then --Healing Drone and refilling its energy
											target.dvars.hull = $ + 4
											target.dvars.energy = $ + 50
										end
										if target.player.skin == "ass" then --restore Almost Super Sonic's depleted stats
											if target.player.normalspeed < skins[target.player.mo.skin].normalspeed then
												target.player.normalspeed = $ + 2
											end
											if target.player.jumpfactor < skins[target.player.mo.skin].jumpfactor then
												target.player.jumpfactor = $ + 2*FRACUNIT/100
											end
											if target.player.actionspd < skins[target.player.mo.skin].actionspd then
												target.player.actionspd = $ + 2
											end
											if target.player.mindash < skins[target.player.mo.skin].mindash then
												target.player.mindash = $ + 2
											end
											if target.player.maxdash < skins[target.player.mo.skin].maxdash then
												target.player.maxdash = $ + 2
											end
										end
										if target.player.skin == "satsr_sonic" then
											target.player.soulgauge = $ + 50
										end
										P_GivePlayerRings(target.player, 2) --Heal the other player
										target.player.pfhealingflag = 2 --Flag to tell the game that Annie is applying accelerated healing to a character other than herself
										end
									end
								end
							end
						else
							P_GivePlayerRings(player, 2) --If Annie can't find another player to heal, she heals herself
							player.pfhealingflag = 4 --Flag to tell the game that Annie is applying accelerated healing to herself
						end
				else
					return
				end
			end
		end
	end
end)

//Emergency Carry (Code to carry other players so they don't have to manually stay within 20-fracunit healing & revival range)
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		/*if player.custom2down == nil then
			player.custom2down = 0
		end
		
		if player.cmd.buttons & BT_CUSTOM2 > 0 then
			player.custom2down = $1 + 1
		else
			player.custom2down = 0
		end*/
		
		if player.custom2down > 1 then
			//Dirfex's carrying code is being used as a placeholder and foundation. The intention is:
				--Annie can freely move around, including jumping and double-jumping (but not spin-dashing) while carrying a player
				--Annie can pick up another player regardless of whether she or the other player are in the air or on the ground
				--Annie receives the SF_NOJUMPSPIN and SF_NOJUMPDAMAGE flags while carrying a player (cannot damage enemies by jumping into them), but can damage enemies with Vixen Vortex
				--The other player can make Annie drop them at any time by pressing Move Left, Move Right, Move Forward, Move Back, Jump, or Spin
				--Annie's movement parameters (normalspeed, thrustfactor, accelstart, acceleration, jumpfactor) are reduced if she is carrying a character above a certain large size or weight class, including robots
				--Annie cannot pick up characters above a certain larger size or weight class at all
			/*if (player.panim == PA_IDLE or player.mo.state == S_PLAY_ANNIE_EMERGENCYCARRY) and not (leveltime%4)
			
			local found = false
			searchBlockmap("objects", function(pmo, mo)
				if not mo.player or not mo.player.valid return end
				if mo.player.panim ~= PA_ABILITY
				and mo.z > pmo.z + pmo.height and mo.z < pmo.z + pmo.height + 55*FRACUNIT
					found = true
					player.mo.state = S_PLAY_ANNIE_EMERGENCYCARRY
					p.dfx.playercarry = (R_PointToDist2(pmo.x, pmo.y, mo.x, mo.y) < 32*FRACUNIT and (mo.z - pmo.z)/FRACUNIT < 80)
				end
			end, p.mo, p.mo.x, p.mo.x + 25*FRACUNIT, p.mo.y, p.mo.y + 25*FRACUNIT)
		
			if player.mo.state == S_PLAY_ANNIE_EMERGENCYCARRY and not found
				player.mo.state = S_PLAY_STND
			end

		end

		//If we entered our carrying position that means we can now carry players, so let's spawn something on top of us to hold 'em
		if player.mo.state == S_PLAY_ANNIE_EMERGENCYCARRY

			player.mo.sprite2 = p.dfx.playercarry and SPR2_DSEG or SPR2_STND
			//We'll spawn an invisible gargoyle on top of Annie to be used as a carrying platform
			if not p.dfx.carrygoyle or (p.dfx.carrygoyle and not p.dfx.carrygoyle.valid)
				if p.dfx.carrygoyle and not p.dfx.carrygoyle.valid then p.dfx.carrygoyle = nil end
				p.dfx.carrygoyle = P_SpawnMobjFromMobj(p.mo, 0, 0, 0, MT_GARGOYLE)
				p.dfx.carrygoyle.flags = $ | MF_NOGRAVITY | MF_NOCLIPHEIGHT & ~MF_PUSHABLE & ~MF_SLIDEME
				p.dfx.carrygoyle.flags2 = $ | MF2_DONTDRAW
				p.dfx.carrygoyle.radius = 8*FRACUNIT
			end

			//Put the carrygoyle where it should be
			P_TeleportMove(p.dfx.carrygoyle, p.mo.x, p.mo.y, p.mo.z + p.mo.height/2)
			
			if p.cmd.forwardmove or p.cmd.sidemove or (p.cmd.buttons & BT_JUMP) or (p.cmd.buttons & BT_SPIN)
				if p.dfx.carrygoyle and p.dfx.carrygoyle.valid then P_RemoveMobj(p.dfx.carrygoyle) end
				p.dfx.carrygoyle = nil
				player.mo.state = S_PLAY_WALK
			end
		else
			if p.dfx.carrygoyle
				if p.dfx.carrygoyle.valid then P_RemoveMobj(p.dfx.carrygoyle) end
				p.dfx.carrygoyle = nil
				end
			end*/
		end
	end
end)

//Revive (Code to resurrect other players)
--First, Annie needs to be able to know where a dead player is and interact with the body, which isn't really possible if they fall through the floor. The ideal solution is to give each skin a few extra sprites (S_CRPS) that show them lying lifelessly on the ground (as well as removing the noclip flag from the dead state). If no such sprite has been drawn for a character, this Player Down marker can be used to show their position, and treated as though it was the deceased player object for the purpose of reviving them or determining if the corpse should be destroyed before deadtime expires.
mobjinfo[MT_PLAYERDOWNMARKER] = { 
	spawnstate = S_PLAYERDOWNMARKER_SPAWNSTATE,
	raisestate = S_PLAYERDOWNMARKER_RAISESTATE,
	deathstate = S_PLAYERDOWNMARKER_DEATHSTATE,
	spawnhealth = 1,
	height = 50*FRACUNIT,
	radius = 40*FRACUNIT,
	flags = MF_NOCLIPTHING
}
states[S_PLAYERDOWNMARKER_SPAWNSTATE] = {
	sprite = SPR_PDWNA,
	frames = FF_TRANS30,
	tics = 1,
	nextstate = S_PLAYERDOWNMARKER_RAISESTATE
}
states[S_PLAYERDOWNMARKER_RAISESTATE] = {
	sprite = SPR_PDWNA,
	frames = FF_TRANS50,
	tics = -1,
	nextstate = S_PLAYERDOWNMARKER_DEATHSTATE
}
states[S_PLAYERDOWNMARKER_DEATHSTATE] = {
	sprite = SPR_PDWNA,
	frames = FF_TRANS30,
	tics = 1,
	nextstate = S_NULL
}

--Next, we need to tinker with the respawn code of other players in order to give Annie time to revive them.
/*Things to do:
	Change default deadtime for players OTHER THAN ANNIE to 10*TICRATE (unless there are 2 or more Annie players in a netgame, in which case they adjust each other's deadtime as well). This should give Annie time to get to players who die near her position and resurrect them.
	Make sure that the dead player's corpse or player down marker counts as a valid dead player for the purpose of being found by P_LookForPlayers, including if the dead player lost their last life and would be getting a Game Over.
	Create a console command for Annie to allow her player to adjust the default deadtime, in case 10 seconds isn't quite enough time.
	Create a console command for OTHER PLAYERS to allow them to adjust their own deadtime and/or manually respawn if other conditions are valid (original (non-Annie-influenced) deadtime expired, lives remaining, etc.), so they're not stuck loitering after death if Annie's player isn't willing to help the team.
	Create a script to override this deadtime adjustment and return to SRB2's default respawn behavior for players who die in lava, death pits, instant-kill sectors (including Phazon), or crushers that only move when triggered and don't auto-reset.
	Create a function to automatically announce Annie's presence in a netgame and inform other players of the deadtime-adjusting console command.
*/

//CV_RegisterVar(newdeadtime{"newdeadtime", 10, CV_SAVE|CV_SHOWMODIF, CV_Natural, ??}) --Annie sets the default deadtime for other players

//COM_AddCommand(editdeadtime{"adjusteddeadtime",}) --Other players are allowed to adjust their own deadtimes if the value set by Annie is too short or too long for their liking

/*addHook("ThinkFrame", function() --Modified deadtime and respawn
	local anniecount = 0
	for player in players.iterate() do
		if player.mo.skin == "annie_belnades" then
			anniecount = $ + 1 --count number of Annie players
		end
-- other stuffs
		if player.mo.skin == "annie_belnades" and anniecount < 2 then
			return --don't run this code for Annie if no other Annie players are present
		end
		if player.mo.skin != "annie_belnades" and anniecount < 1 then
			return --don't run this code if no Annie players are present
		end
		
		if player.reviveprogress == nil then
			player.reviveprogress = 0 --setting up revive progress variable (this is also done within the revival hook, but I'm doing it here as well so that resetting reviveprogress within this hook won't cause nil variable errors)
		end
		
		if --if player has just been killed
		and P_CheckDeathPitCollide(player.mo) == false --if the player wasn't killed by a death pit
		and P_CheckSolidLava(player.mo, player.mo.floorrover) == false --and wasn't killed by lava
		and player.sector.special ~= 8 then --and wasn't killed by an instakill sector
			--retain corpse as a valid object so that the rest of the revival setup and procedure doesn't get treated as nonsense by Lua
			P_DoPlayerPain(player, source, inflictor) --do knockback for the killing blow
			player.playerstate = PST_DEAD --shift from pain state to dead state (you're dead, not just hurt, silly)
			player.deadtime = adjusteddeadtime --set the starting deadtime to the amount chosen via console command
		end
		
		if player.deadtime > 0 then --if the player is currently dead and has not been revived or naturally respawned
			if P_CheckDeathPitCollide(player.mo) == true --if the corpse wound up in a death pit
			or P_CheckSolidLava(player.mo, player.mo.floorrover) == true --or the corpse wound up in lava
			--or the corpse got crushed by something that won't move off of it
			or player.sector.special == 8 then --or the corpse wound up in an insta-kill sector
				--remove corpse, return to default SRB2 respawn behavior
			end
		end
		
		if player.deadtime == 0 then
			player.reviveprogress = 0 --revive progress doesn't carry over between deaths, regardless of the success or failure of revival
		end
	end	
end)*/

--With that done, now we can move on to the revival procedure itself.
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		/*if player.custom3down == nil then
			player.custom3down = 0
		end
		
		if player.cmd.buttons & BT_CUSTOM3 > 0 then
			player.custom3down = $1 + 1
		else
			player.custom3down = 0
		end*/
		
		if player.custom3down > 1 -- if custom3 is currently being pressed
		and player.custom1down == 0 --and custom1 is not being pressed
		and player.mo.energy >= 50*FRACUNIT/6 then -- and enough energy is available to at least make progress on revival
			if not (leveltime%TICRATE) then
				if player.mo.energy >= 50*FRACUNIT/6 then
					if P_LookForPlayers(player.mo, 20*FRACUNIT, true, false) == true then --Is there another player within 20 fracunits of Annie?
						local target = player.mo.target.player --defining the player found in P_LookForPlayers as the target for the rest of the revival script
						if target.player and target.player.valid and target.player.playerstate == PST_DEAD and target.player.mo ~= player.mo then --Is there a dead player OTHER THAN Annie herself within 20 fracunits of her?
							if G_CoopGametype() == true
							or (G_GametypeHasTeams() == true and player.ctfteam == target.player.ctfteam) then --Is that other player on the same team as Annie?
								if P_CheckSight(player.mo, target.player) == true then --Can Annie actually see the other player?
									if target.player.reviveprogress == nil then
										target.player.reviveprogress = 1*FRACUNIT --Give the target the reviveprogress variable (starting at 1 since that much progress has been made)
										target.player.deadtime = $ - 2*TICRATE --Freeze deadtime
										player.mo.energy = $ - 50*FRACUNIT/6 --Drain Annie's energy by an amount equal to 1/6 of the full revive cost of 50
										S_StartSoundAtVolume(player.mo, sfx_heal, 150, nil)
									else
										target.player.reviveprogress = $ + 1*FRACUNIT --Increment the target's reviveprogress variable by 1 out of 6
										target.player.deadtime = $ - 2*TICRATE --Freeze deadtime
										player.mo.energy = $ - 50*FRACUNIT/6 --Drain Annie's energy by an amount equal to 1/6 of the full revive cost of 50
										S_StartSoundAtVolume(player.mo, sfx_heal, 150, nil)
										if target.player.reviveprogress >= 6*FRACUNIT then
											target.player.playerstate = PST_LIVE --revive the other player (skipping PST_REBORN in order to avoid things like sending them to their last spawn point or checkpoint or decrementing their lives)
											target.player.deadtime = 0 --the revived player is no longer dead, so deadtime is obviously 0
											S_StopSoundByID(player.mo, sfx_heal)
											chatprint(target.player.name.." has been revived!")
											target.player.reviveprogress = 0 --reset progress for the next revival
											return
										else
											repeat until target.player.reviveprogress >= 6*FRACUNIT or player.custom3down == 0 or player.mo.energy < 50*FRACUNIT/6
										end
										
										if target.player.playerstate == PST_REBORN then --If Annie attempts to revive another player, but fails to complete the process before they naturally respawn
											target.player.reviveprogress = 0 --reset progress for the next revival
										end
									end
								end
							end
						end
					else
						return
					end
				else
					return
				end
			end
		end
	end
end)

//Accelerated Revive (Code to resurrect other players faster, but not be able to move)
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		/*if player.custom3down == nil then
			player.custom3down = 0
		end
		
		if player.cmd.buttons & BT_CUSTOM3 > 0 then
			player.custom3down = $1 + 1
		else
			player.custom3down = 0
		end*/
		
		if player.custom3down > 1 -- if custom3 is currently being pressed
		and player.custom1down > 1 -- and if custom1 is also currently being pressed
		and player.custom3down > player.custom1down -- and if custom3 was pressed *before* custom1
		and player.mo.energy >= 50*FRACUNIT/4 then -- and enough energy is available to at least make progress on revival
			if (leveltime%TICRATE) then
				if player.mo.energy >= 50*FRACUNIT/4 then
					if P_LookForPlayers(player.mo, 20*FRACUNIT, true, false) == true then --Is there another player within 20 fracunits of Annie?
						local target = player.mo.target.player --defining the player found in P_LookForPlayers as the target for the rest of the revival script
						if target.player and target.player.valid and target.player.playerstate == PST_DEAD and target.player.mo ~= player.mo then --Is there a dead player OTHER THAN Annie herself within 20 fracunits of her?
							if G_CoopGametype() == true
							or (G_GametypeHasTeams() == true and player.ctfteam == target.player.ctfteam) then --Is that other player on the same team as Annie?
								if P_CheckSight(player.mo, target.player) == true then --Can Annie actually see the other player?
									P_ResetPlayer(player)
									player.pflags = $1|PF_FULLSTASIS --Lock Annie in place during accelerated revival (the process costs 50 energy either way; the immobility is the real downside)
								end
							end
						end
					end
				end
			end
			if not (leveltime%TICRATE) then
				if player.mo.energy >= 50*FRACUNIT/4 then
					if P_LookForPlayers(player.mo, 20*FRACUNIT, true, false) == true then --Is there another player within 20 fracunits of Annie?
						local target = player.mo.target.player --defining the player found in P_LookForPlayers as the target for the rest of the revival script
						if target.player and target.player.valid and target.player.playerstate == PST_DEAD and target.player.mo ~= player.mo then --Is there a dead player OTHER THAN Annie herself within 20 fracunits of her?
							if G_CoopGametype() == true
							or (G_GametypeHasTeams() == true and player.ctfteam == target.player.ctfteam) then --Is that other player on the same team as Annie?
								if P_CheckSight(player.mo, target.player) == true then --Can Annie actually see the other player?
									P_ResetPlayer(player)
									player.pflags = $1|PF_FULLSTASIS --Lock Annie in place during accelerated revival (the process costs 50 energy either way; the immobility is the real downside)
									if target.player.reviveprogress == nil then
										target.player.reviveprogress = 3*FRACUNIT/2 --Give the target the reviveprogress variable (starting at 1.5 since that much progress has been made)
										target.player.deadtime = $ - 2 --Freeze deadtime
										player.mo.energy = $ - 50*FRACUNIT/4 --Drain Annie's energy by an amount equal to 1/4 of the full revive cost of 50
										S_StartSoundAtVolume(player.mo, sfx_heal, 180, nil)
									else
										target.player.reviveprogress = $ + 3*FRACUNIT/2 --Increment the target's reviveprogress variable by 1.5 out of 6
										target.player.deadtime = $ - 2*TICRATE --Freeze deadtime
										player.mo.energy = $ - 50*FRACUNIT/4 --Drain Annie's energy by an amount equal to 1/4 of the full revive cost of 50
										S_StartSoundAtVolume(player.mo, sfx_heal, 180, nil)
										if target.player.reviveprogress >= 6*FRACUNIT then
											target.player.playerstate = PST_LIVE --revive the other player (skipping PST_REBORN in order to avoid things like sending them to their last spawn point or checkpoint or decrementing their lives)
											chatprint(target.player.name.." has been revived!")
											S_StopSoundByID(player.mo, sfx_heal)
											target.player.reviveprogress = 0 --reset progress for the next revival
											return
										else
											repeat until target.player.reviveprogress >= 6*FRACUNIT or player.custom3down == 0 or player.mo.energy < 50*FRACUNIT/4
										end
										
										if target.player.playerstate == PST_REBORN then --If Annie attempts to revive another player, but fails to complete the process before they naturally respawn
											target.player.reviveprogress = 0 --reset progress for the next revival
										end
									end
								end
							end
						end
					else
						return
					end
				else
					return
				end
			end
		end
	end
end)

--The current revival rates are actually the rates for if Annie is carrying her patient; revival is meant to be 25% faster (4 seconds unaccelerated, 3 seconds accelerated) if Annie isn't carrying the other player as a trade-off for having to deal with the awkwardness of staying very close to an object that isn't moving with you. I can't really implement the distinction until I know how the game would detect whether or not Annie is carrying the target.
--Perhaps there can also be a Metroid Vanguard Easter Egg where Fang's corpse in ACZ4 can be resurrected, creating a Fang bot to assist Annie and Samus for the rest of the game and causing some minor changes to the plot due to Fang's presence. That would be a lot of work, though... and I would need to collaborate with Golden Shine on it.

addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if ((player.custom1down == 0 and player.custom3down == 0) or player.mo.energy < 5*FRACUNIT/2)
		and S_SoundPlaying(player.mo, sfx_heal) == true then
			S_StopSoundByID(player.mo, sfx_heal) --make sure healing sound stops when healing stops
		end
	end
end)

//Tough Body (Code to retain some Rings upon taking damage)
addHook("PlayerThink", function(player) --Defining the ring retention variable that will be used to hold on to a portion of the player's rings
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if player.mo.ringretention == nil then
			player.mo.ringretention = 0
		end
		
		if player.mo.ringreturndelay == nil then
			player.mo.ringreturndelay = 0
		end
		
		if player.mo.ringretention > 0
		//and player.powers[pw_flashing] > 0 then
		and P_PlayerInPain(player) == true then
			player.mo.ringreturndelay = 10
		end
		
		if player.mo.ringreturndelay > 0
		and player.mo.ringretention > 0 then
			if (leveltime%TICRATE) then
				player.mo.ringreturndelay = $ - 1 --Wait 10 tics for the MobjDamage event to end before attempting to return Rings
			end
		end
		
		if player.mo.ringretention > 0
		and player.powers[pw_flashing] < 2*TICRATE --Rings should be returned BEFORE mercy invulnerability wears off
		and player.mo.ringreturndelay == 0 then
			P_GivePlayerRings(player, player.mo.ringretention)
				/*player = player --Run the function on Annie
				amount = player.mo.ringretention --Return Rings equal to the value of player.mo.ringretention */
				--End the give rings function
			player.mo.ringretention = 0 --Set player.mo.ringretention to 0 AFTER the Rings are returned and effectively retained
		end
	end
end)

addHook("MobjDamage", function(target, inflictor, source, damage, damagetype) --Target = player.mo (Annie), inflictor & source = any, damage = any, damagetype = any (non-lethal); function should run when Annie takes a hit with Rings
	if target.player and target.player.valid then
	
		if target.player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if target.player.rings >= 6 and target.player.rings < 50 and target.player.powers[pw_shield] == 0 then
			target.player.mo.ringretention = 5
			P_PlayerRingBurst(target.player, (target.player.rings - 5))
				/*player = player
				amount = player.rings - 5 --player drops all but 5 rings */
			//target.player.rings = 5
			target.player.powers[pw_flashing] = 3*TICRATE
			print(target.player.mo.ringretention.." Rings to be retained")
		end
		
		if target.player.rings >= 50 and target.player.powers[pw_shield] == 0 then
			target.player.mo.ringretention = (target.player.rings / 2) //should be FixedCeil(target.player.rings / 2), but that caused instakill issues
			P_PlayerRingBurst(target.player, (target.player.rings / 2)) //should be FixedFloor(target.player.rings / 2), but that caused instakill issues
				/*player = player
				amount = FixedFloor(player.rings / 2) --player drops half of their rings */
			//target.player.rings = (target.player.rings / 2) //should be FixedCeil(target.player.rings / 2), but that caused instakill issues
			target.player.powers[pw_flashing] = 3*TICRATE
			print(target.player.mo.ringretention.." Rings to be retained")
		end
	end
end, MT_PLAYER)

addHook("ShouldDamage", function(target, inflictor, source, damage, damagetype) --These durability hooks seem to screw with the usual mercy invincibility that characters are supposed to get, so here's a safeguard against juggling
	if target.player and target.player.valid then
	
		if target.player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if damagetype ~= DMG_CRUSHED
		and damagetype ~= DMG_DROWNED
		and damagetype ~= DMG_SPACEDROWN
		and damagetype ~= DMG_DEATHPIT
		and (P_PlayerInPain(target.player) == true or target.player.powers[pw_flashing] > 0 or target.player.powers[pw_invulnerability] > 0 or target.player.powers[pw_super] > 0) then --if the player SHOULD be immune to standard damage due to the base game code
			//print("This hit should not be happening!")
			return false
		end
	end
end, MT_PLAYER)

//Death Defiance
addHook("PlayerThink", function(player) --Defining the "death defiance" variable that will be used to delay energy regeneration after a Hard To Kill passive takes effect and setting up how it depletes
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if player.mo.deathdefiance == nil then
			player.mo.deathdefiance = 0
		end
		
		if player.mo.deathdefiance > 0
		and player.powers[pw_flashing] == 0 --Death Defiance doesn't run down during mercy invulnerability
		//and player.panim ~= PA_KNOCKDOWN or PA_WALLPLASTERED then
		and player.mo.squished == 0
		and player.mo.drowndelay == 0
		and player.mo.suffocatedelay == 0
		and player.mo.pzpaintime == 0 then
			if not (leveltime%TICRATE) then
				player.mo.deathdefiance = $ - 1 --Wait 3 seconds after survival (or 1 second after avoiding drowning/suffocation) to resume usual energy replenishment routine
			end
		end
	end
end)

//Hard to Kill - 0 Rings (Code to survive upon being damaged with 0 rings and no shield at the cost of 25% energy)
addHook("ShouldDamage", function(target, inflictor, source, damage, damagetype) --Target = player.mo (Annie), inflictor & source = any, damage = any, damagetype = any non-instakill that reduces health to 0 (Annie takes ordinary damage with no Rings and no shield)
	if target.player and target.player.valid then
	
		if target.player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if target.player.rings == 0 and target.player.powers[pw_shield] == 0 and target.player.mo.ringretention == 0 --this hook should only run if the player takes a hit that would otherwise kill them
		and target.player.mo.energy >= 25*FRACUNIT --Annie can only heal her wounds so often before she's too exhausted to go on
		and P_PlayerInPain(target.player) == false --Clause against running this hook multiple times in a row
		and damagetype ~= DMG_CRUSHED
		and damagetype ~= DMG_DROWNED
		and damagetype ~= DMG_SPACEDROWN
		and damagetype ~= DMG_INSTAKILL
		and damagetype ~= DMG_DEATHPIT then --Inherently-lethal damage types get their own hooks (except for death pits, which still kill Annie regardless of her energy)
			print("Oh no! You have no Rings and no shield!")
			P_DoPlayerPain(target.player, source, inflictor)
			P_PlayerWeaponAmmoBurst(target.player)
			P_PlayerEmeraldBurst(target.player, false)
			P_PlayerFlagBurst(target.player, false)
			target.player.powers[pw_flashing] = 3*TICRATE --player should get mercy invulnerability so they don't get juggled
			target.player.losstime = $ + 10*TICRATE
			target.player.timeshit = $ + 1 --player does not die, but the damage still counts against them for the purpose of variables tracking how often they have been hit
			//print(target.player.timeshit.." hits taken")
			target.player.mo.energy = $ - 25*FRACUNIT --ability cost (reduce energy by 25)
			target.player.mo.deathdefiance = 3 --Stop automatic energy regeneration during post-hit invulnerability and for 3 seconds afterwards --to make this into more of an insurance policy against mistakes rather than a license to be reckless
			return false
		else
			return
		end
	end
end, MT_PLAYER)

//Hard to Kill - Drowning (Code to survive upon air timer expiring underwater or in space at the expense of 20% energy per second)
--I had a whole bevy of complicated stuff set up (see "Annie - Hard to Kill - Drowning (Big Complicated Setup)" in the Rough Drafts folder) that wasn't working until Zarosguth pointed out a much simpler approach I could take instead...
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if player.mo.drowndelay == nil then
			player.mo.drowndelay = 0
		end
		
		if player.mo.suffocatedelay == nil then
			player.mo.suffocatedelay = 0
		end
		
		if player.powers[pw_underwater] ~= 0 --if the player is underwater
		and player.powers[pw_underwater] <= 10 --if the player is on the verge of drowning
		and player.mo.energy >= 20*FRACUNIT then --and the player has enough energy
			player.mo.energy = $ - 20*FRACUNIT --decrease player.mo.energy by 20*FRACUNIT (no "if not (leveltime%TICRATE)" needed here, player.powers[pw_underwater] already does that)
			player.powers[pw_underwater] = $ + 1*TICRATE --add 1 second to the air timer
			player.mo.deathdefiance = 1 --prevent energy regeneration until air is acquired
			player.mo.drowndelay = 1 --prevent deathdefiance from decrementing
		end
		
		if player.powers[pw_underwater] == 0 --if the player is no longer underwater
		or player.powers[pw_underwater] >= 2*TICRATE then --or if the player has gotten air
			player.mo.drowndelay = 0 --allow deathdefiance to drop back to 0
		end
		
		if player.powers[pw_spacetime] ~= 0 --if the player is in space
		and player.powers[pw_spacetime] <= 10 --if the player is on the verge of suffocating
		and player.mo.energy >= 20*FRACUNIT then --and the player has enough energy
			player.mo.energy = $ - 20*FRACUNIT --decrease player.mo.energy by 20*FRACUNIT (no "if not (leveltime%TICRATE)" needed here, player.powers[pw_spacetime] already does that)
			player.powers[pw_spacetime] = $ + 1*TICRATE --add 1 second to the air timer
			player.mo.deathdefiance = 1 --prevent energy regeneration until air is acquired
			player.mo.suffocatedelay = 1 --prevent deathdefiance from decrementing
		end
		
		if player.powers[pw_spacetime] == 0 --if the player is no longer in space
		or player.powers[pw_spacetime] >= 2*TICRATE then --or if the player has gotten air
			player.mo.suffocatedelay = 0 --allow deathdefiance to drop back to 0
		end
	end
end)

//Hard to Kill - Crush (Code to survive being crushed (instead being put in a knocked-down state) at the expense of taking damage and 25% energy (with the cost increasing to 50% energy with 0 Rings and no shield), getting up when the crusher moves off of Annie (or dying anyway if it doesn't move off of Annie within 5 seconds of crushing))
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if player.mo.squished == nil then
			player.mo.squished = 0 --variable that counts how long Annie has been crushed
		end
		
		/*if not (player.pflags & PF_FULLSTASIS) then
			player.mo.squished = 0 --the original plan was to reset player.mo.squished to 0 the instant Annie was no longer being crushed, as detected by the PF_FULLSTASIS flag imposed by the crush resist ShouldDamage hook, but it turned out that PF_FULLSTASIS was being removed on every tic
		end*/
		
		if player.mo.rebound == nil then
			player.mo.rebound = 0 --variable that resets player.mo.squished after Annie has spent enough time not being crushed
		end
		
		if player.mo.squished ~= 0 then
			if (leveltime%TICRATE) then
				if not (player.pflags & PF_FULLSTASIS) then
					player.mo.rebound = $ + 1
				end
			end
		end
		
		if player.mo.rebound == 5*TICRATE/2 then --Once the player has not been crushed for a total of 2.5 seconds since the initial crushing (I initially set this to 7.5 before I figured out a way to stop player.mo.rebound from counting up while player.mo.squished is counting up (i.e. while Annie is currently crushed))
			player.mo.squished = 0 --reset player.mo.squished
			player.mo.rebound = 0 --reset player.mo.rebound
		end
		
		--I would have code here to make player.mo.squished begin counting up at a rate of 1 per second while Annie is squished between two solid surfaces (i.e. conditions that would trigger DMG_CRUSHED), and kill her once its value reaches 5, but the ShouldDamage hook takes care of that for me.
	end
end)

/*Pseudocode
addHook("ShouldDamage", function(target, inflictor, source, damage, damagetype) --Target = player.mo (Annie), inflictor & source = any, damage = any, damagetype = DMG_CRUSHED
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if player.mo.energy >= 25*FRACUNIT
		and player.rings > 0 or player.powers[pw_shield] > 0 then
			if damagetype == DMG_CRUSHED then
				Prevent death
				player.mo.energy = $ - 25*FRACUNIT --reduce player.mo.energy by 25
				enter animation state PA_KNOCKDOWN or PA_WALLPLASTERED (depending on whether Annie was crushed between a floor and ceiling or between two walls)
				immobilize player --this is not flat-out ignoring crushing via godmode or noclip, this is staying alive even while being crushed
				Take damage (lose Rings or shield) --run proper MobjDamage to remove Rings or shield
				player.mo.deathdefiance = 3 -- stop replenishing energy during crush and wait for 3 seconds after crush ends to resume energy replenishment
				Wait for the crusher to move to a position that is no longer crushing Annie --return to player.squished PlayerThink
					If Annie is still crushed after 5 seconds, she dies
					Once the crusher moves off her position, Annie gets up and can start moving again
			end
		end
			
		if player.mo.energy >= 50*FRACUNIT
		and player.rings == 0 and player.powers[pw_shield] == 0 then
			if damagetype == DMG_CRUSHED then
				Prevent death
				player.mo.energy = $ - 50 --reduce player.mo.energy by 50
				enter animation state PA_KNOCKDOWN or PA_WALLPLASTERED (depending on whether Annie was crushed between a floor and ceiling or between two walls)
				immobilize player --this is not flat-out ignoring crushing via godmode or noclip, this is staying alive even while being crushed
				player.mo.deathdefiance = 3 -- stop replenishing energy during crush and wait for 3 seconds after crush ends to resume energy replenishment
				Wait for the crusher to move to a position that is no longer crushing Annie --return to player.mo.squished PlayerThink
					If Annie is still crushed after 5 seconds, she dies
					Once the crusher moves off her position, Annie gets up and can start moving again
			end
		end
	end
end, MT_PLAYER)*/

addHook("ShouldDamage", function(target, inflictor, source, damage, damagetype) --Target = player.mo (Annie), inflictor & source = any, damage = any, damagetype = DMG_CRUSHED
	if target.player.mo and target.player.mo.valid then
	
		if target.player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if damagetype == DMG_CRUSHED
		and target.player.mo.squished == 0 --if player was just crushed on this tic
		and target.player.mo.energy >= 25*FRACUNIT
		and (target.player.rings > 0 or target.player.powers[pw_shield] > 0) then
			//P_DoPlayerPain(target.player, source, inflictor)
			/*P_PlayerWeaponAmmoBurst(target.player)
			P_PlayerEmeraldBurst(target.player, false)
			P_PlayerFlagBurst(target.player, false)*/
			target.player.losstime = $ + 10*TICRATE
			target.player.timeshit = $ + 1 --player does not die, but the damage still counts against them for the purpose of variables tracking how often they have been hit
			if (target.player.powers[pw_shield] & SH_FORCEHP) then --if the player has a Force Shield with more than 1 HP
				target.player.powers[pw_shield] = SH_FORCE --Damage the Force Shield
				P_SpawnShieldOrb(target.player) --make sure that the proper Force Shield sprite is shown
			elseif target.player.powers[pw_shield] > 0 and not (target.player.powers[pw_shield] & SH_FORCEHP) then --if the player has a damaged Force Shield or a shield other than a Force Shield
				P_RemoveShield(target.player) --remove the shield
				S_StartSound(target.player.mo, sfx_s3k35, nil)
			elseif (target.player.rings >= 6 and target.player.rings < 50) and target.player.powers[pw_shield] == 0 then --Tough Body
				//target.player.mo.ringretention = 5
				//P_PlayerRingBurst(target.player, (target.player.rings - 5)) --I wanted the player to drop Rings upon being crushed, but that just led to the Rings immediately being re-collected.
				P_PlayerWeaponAmmoBurst(target.player)
				P_PlayerEmeraldBurst(target.player, false)
				P_PlayerFlagBurst(target.player, false)
				S_StartSound(target.player.mo, sfx_s3kb9, nil)
				target.player.rings = 5
				//print(target.player.mo.ringretention.." Rings to be retained")
			elseif target.player.rings >= 50 and target.player.powers[pw_shield] == 0 then --Tough Body
				//target.player.mo.ringretention = (target.player.rings / 2) //should be FixedCeil(target.player.rings / 2), but that caused instakill issues
				//P_PlayerRingBurst(target.player, (target.player.rings / 2)) //should be FixedFloor(target.player.rings / 2), but that caused instakill issues
				P_PlayerWeaponAmmoBurst(target.player)
				P_PlayerEmeraldBurst(target.player, false)
				P_PlayerFlagBurst(target.player, false)
				S_StartSound(target.player.mo, sfx_s3kb9, nil)
				target.player.rings = (target.player.rings / 2) //should be FixedCeil(target.player.rings / 2), but that caused instakill issues
				//print(target.player.mo.ringretention.." Rings to be retained")
			elseif target.player.rings <= 5 and target.player.powers[pw_shield] == 0 then
				//P_PlayerRingBurst(target.player, target.player.rings)
				P_PlayerWeaponAmmoBurst(target.player)
				P_PlayerEmeraldBurst(target.player, false)
				P_PlayerFlagBurst(target.player, false)
				S_StartSound(target.player.mo, sfx_s3kb9, nil)
				target.player.rings = 0
			end
			target.player.mo.energy = $ - 25*FRACUNIT --Reduce player.mo.energy by 25
			target.player.mo.deathdefiance = 3 --Pause energy regeneration for 3 seconds
			target.player.pflags = $1|PF_FULLSTASIS --Paralyze the player
			target.player.mo.squished = 1 --Start the squished timer (the game checks for crushing once per tic)
			return false
		end
			
		if damagetype == DMG_CRUSHED
		and target.player.mo.squished == 0 --if player was just crushed on this tic
		and target.player.mo.energy >= 50*FRACUNIT
		and (target.player.rings == 0 and target.player.powers[pw_shield] == 0) then
			//P_DoPlayerPain(target.player, source, inflictor)
			P_PlayerWeaponAmmoBurst(target.player)
			P_PlayerEmeraldBurst(target.player, false)
			P_PlayerFlagBurst(target.player, false)
			P_PlayRinglossSound(target.player.mo, target.player)
			target.player.losstime = $ + 10*TICRATE
			target.player.timeshit = $ + 1 --player does not die, but the damage still counts against them for the purpose of variables tracking how often they have been hit
			target.player.mo.energy = $ - 50*FRACUNIT --Reduce player.mo.energy by 50
			target.player.mo.deathdefiance = 3 --Pause energy regeneration for 3 seconds
			target.player.pflags = $1|PF_FULLSTASIS --Paralyze the player
			target.player.mo.squished = 1 --Start the squished timer (the game seems to check for crushing once per tic)
			return false
		end
		
		if damagetype == DMG_CRUSHED --prevent continued crushing from draining more energy or Rings
		and target.player.mo.squished ~= 0 --if the player was already crushed on the previous tic
		and target.player.mo.squished < 5*TICRATE then --and the player has not been crushed for 5 consecutive seconds
			target.player.pflags = $1|PF_FULLSTASIS --Keep the player paralyzed and prevent player.mo.squished from resetting
			target.player.mo.squished = $ + 1 --Increment the squished timer (when it reaches 5*TICRATE, this function will no longer return false and Annie will die)
			if target.player.mo.rebound >= 2*TICRATE/3 then
				target.player.mo.rebound = $ - 5 --prevent rebound from filling above a certain point while the player is crushed
			end
			return false
		end
	end
end, MT_PLAYER)

//Hard to Kill - Instant Kill (Code to survive touching instant-kill sectors at the expense of taking damage and 25% energy (with the cost increasing to 50% energy with 0 rings and no shield))
addHook("ShouldDamage", function(target, inflictor, source, damage, damagetype) --Target = player.mo (Annie), inflictor & source = any, damage = any, damagetype = DMG_INSTAKILL
	if target.player.mo and target.player.mo.valid then
	
		if target.player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if damagetype == DMG_INSTAKILL
		and target.player.mo.energy >= 25*FRACUNIT
		and (target.player.rings > 0 or target.player.powers[pw_shield] > 0) then
			P_DoPlayerPain(target.player, source, inflictor)
			/*P_PlayerWeaponAmmoBurst(target.player)
			P_PlayerEmeraldBurst(target.player, false)
			P_PlayerFlagBurst(target.player, false)*/
			target.player.losstime = $ + 10*TICRATE
			target.player.timeshit = $ + 1 --player does not die, but the damage still counts against them for the purpose of variables tracking how often they have been hit
			target.player.powers[pw_flashing] = 3*TICRATE
			if (target.player.powers[pw_shield] & SH_FORCEHP) then --if the player has a Force Shield with more than 1 HP
				target.player.powers[pw_shield] = SH_FORCE --Damage the Force Shield
				P_SpawnShieldOrb(target.player) --make sure that the proper Force Shield sprite is shown
			elseif target.player.powers[pw_shield] > 0 and not (target.player.powers[pw_shield] & SH_FORCEHP) then --if the player has a damaged Force Shield or a shield other than a Force Shield
				P_RemoveShield(target.player) --remove the shield
				S_StartSound(target.player.mo, sfx_s3k35, nil)
			elseif (target.player.rings >= 6 and target.player.rings < 50) and target.player.powers[pw_shield] == 0 then --Tough Body
				target.player.mo.ringretention = 5
				P_PlayerRingBurst(target.player, (target.player.rings - 5))
				P_PlayerFlagBurst(target.player, false)
				S_StartSound(target.player.mo, sfx_s3kb9, nil)
				target.player.rings = 5
				print(target.player.mo.ringretention.." Rings to be retained")
			elseif target.player.rings >= 50 and target.player.powers[pw_shield] == 0 then --Tough Body
				target.player.mo.ringretention = (target.player.rings / 2) //should be FixedCeil(target.player.rings / 2), but that caused instakill issues
				P_PlayerRingBurst(target.player, (target.player.rings / 2)) //should be FixedFloor(target.player.rings / 2), but that caused instakill issues
				P_PlayerFlagBurst(target.player, false)
				S_StartSound(target.player.mo, sfx_s3kb9, nil)
				target.player.rings = (target.player.rings / 2) //should be FixedCeil(target.player.rings / 2), but that caused instakill issues
				print(target.player.mo.ringretention.." Rings to be retained")
			elseif target.player.rings <= 5 and target.player.powers[pw_shield] == 0 then
				P_PlayerRingBurst(target.player, target.player.rings)
				P_PlayerFlagBurst(target.player, false)
				S_StartSound(target.player.mo, sfx_s3kb9, nil)
				target.player.rings = 0
			end
			target.player.mo.energy = $ - 25*FRACUNIT --Reduce player.mo.energy by 25
			target.player.mo.deathdefiance = 3 --Pause energy regeneration for 3 seconds
			return false
		end
			
		if damagetype == DMG_INSTAKILL
		and target.player.mo.energy >= 50*FRACUNIT
		and (target.player.rings == 0 and target.player.powers[pw_shield] == 0) then
			P_DoPlayerPain(target.player, source, inflictor)
			P_PlayerWeaponAmmoBurst(target.player)
			P_PlayerEmeraldBurst(target.player, false)
			P_PlayerFlagBurst(target.player, false)
			P_PlayRinglossSound(target.player.mo, target.player)
			target.player.powers[pw_flashing] = 3*TICRATE
			target.player.losstime = $ + 10*TICRATE
			target.player.timeshit = $ + 1 --player does not die, but the damage still counts against them for the purpose of variables tracking how often they have been hit
			target.player.mo.energy = $ - 50*FRACUNIT --Reduce player.mo.energy by 50
			target.player.mo.deathdefiance = 3 --Pause energy regeneration for 3 seconds
			return false
		end
		
		if damagetype == DMG_INSTAKILL
		and P_PlayerInPain(target.player) == true then
			return false --don't drain energy repeatedly
		end
	end
end, MT_PLAYER)

//Hard to Kill - Phazon (the Phazon in ACZ4 of Metroid Vanguard is coded differently from other instant kill floors)
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
		
		if player.mo.energy >= 20*FRACUNIT then
			player.sam_phazonsuit = true --prevent Phazon damage while Annie has sufficient energy
		else
			player.sam_phazonsuit = false
		end
		
		if player.mo.pzpaintime == nil then --player.mo.pzpaintime gets defined in Metroid Vanguard's source code, but it makes sense to define it here as well so that it doesn't return errors if Metroid Vanguard isn't loaded.
			player.mo.pzpaintime = 0
		end
		
		if player.mo.phazonexposure == nil then --variable to track how long the player has been in Phazon
			player.mo.phazonexposure = 0
		end
		
		if not ((player.mo.eflags & MFE_UNDERWATER) or (player.mo.eflags & MFE_TOUCHWATER)) --if the player is not in water
		and player.mo.pzpaintime ~= 0
		and player.mo.phazonexposure > 0 then --if the player was in Phazon on the previous tic
			player.mo.pzpaintime = 0 --Phazon appears to be a water sector property, so this should deactivate the Phazon timer upon exiting Phazon and prevent energy from being drained while the player isn't in Phazon
			player.mo.phazonexposure = 0
		end
	end
end)

addHook("LinedefExecute", function(line, mobj, sector)
	if mobj.valid and mobj.player then --specifying that this function runs when the player ends up in Phazon (I couldn't specify that the mobj is the player in the function line itself without causing an error)
		local player = mobj.player
		if player.mo and player.mo.valid then
			
			if player.mo.skin ~= "annie_belnades" then --Double-checking that the player is Annie
				return
			end
			
			if (player.mo.eflags & MFE_TOUCHWATER) then --on the very first tic of Phazon exposure
				if player.mo.pzpaintime == 0 --if the Phazon pain timer is inactive
				and player.mo.phazonexposure == 0 then --if the player is just entering Phazon
					player.mo.pzpaintime = 44 --start the timer
					player.mo.phazonexposure = 1
				end
			end
			
			if player.mo.pzpaintime >= 44 --if the player has just entered Phazon
			and ((player.mo.eflags & MFE_TOUCHWATER) or (player.mo.eflags & MFE_UNDERWATER)) --this code should run on the very first tic of Phazon exposure (Phazon is a water sector)
			and player.mo.phazonexposure == 1
			and player.mo.energy >= 20*FRACUNIT then --energy is required to stave off Phazon
				P_GivePlayerRings(player, 10) --add 10 Rings to the player to counteract first-tic Ring loss/death (these Rings should be immediately lost as the first tic of Phazon exposure takes them away)
				P_StartQuake(12*FRACUNIT, 2)
				player.mo.energy = $ - 20*FRACUNIT --energy cost of staving off Phazon
				player.mo.deathdefiance = 2 --prevent energy from regenerating until the player has exited Phazon (and for 2 seconds afterwards)
			end
			
			if (player.mo.eflags & MFE_UNDERWATER)
			and player.mo.pzpaintime ~= 0 then
				if (leveltime%TICRATE) then
					player.mo.phazonexposure = $ + 1 --Track how long the player has been in Phazon so that the code that runs when the player enters Phazon doesn't run when the player exits Phazon
				end
			end
		
			/*if (player.mo.eflags & MFE_UNDERWATER)
			and player.mo.pzpaintime ~= 0 then
				if (leveltime%TICRATE) then
					player.mo.pzpaintime = $ - 1 --Since Annie has a "Phazon Suit" to prevent Phazon from harming her while she has sufficient energy to resist, I think I need to decrement pzpaintime myself.
				end
			end*/
		
			if player.mo.pzpaintime == 1 --main Phazon delay loop
			and player.mo.phazonexposure > 1
			and player.mo.energy >= 20*FRACUNIT then --energy is required to stave off Phazon
				player.mo.pzpaintime = $ + 44 --loop the timer
				P_StartQuake(12*FRACUNIT, 2)
				player.mo.energy = $ - 20*FRACUNIT --energy cost of staving off Phazon
				player.mo.deathdefiance = 2 --Pause energy regeneration for 2 seconds after Phazon exposure
			end
			
			if player.mo.pzpaintime == 1 --Phazon loop without energy (just in case the code to shut off Annie's "Phazon Suit" when her energy drops below 20 doesn't work)
			and player.mo.phazonexposure > 1
			and player.mo.energy < 20*FRACUNIT then --if the player doesn't have enough energy to stave off Phazon
				player.mo.pzpaintime = $ + 44
				P_StartQuake(12*FRACUNIT, 2)
				if (player.powers[pw_shield] & SH_FORCEHP) then
					player.powers[pw_shield] = SH_FORCE --weaken a Force Shield
					P_SpawnShieldOrb(target.player) --make sure that the proper Force Shield sprite is shown
				elseif player.powers[pw_shield] > 0 and not (player.powers[pw_shield] & SH_FORCEHP) then
					P_RemoveShield(player) --remove a non-Force shield or weakened Force Shield
				elseif player.powers[pw_shield] == 0 and player.rings > 0 then
					if player.rings >= 10 then
						player.rings = $ - 10 --remove rings
					else
						player.rings = 0
					end
				elseif player.powers[pw_shield] == 0 and player.rings == 0 then
					P_KillMobj(player.mo, nil, nil) --No energy and no Rings? Phazon will kill Annie.
				end
				player.mo.deathdefiance = 2 --keep energy regeneration paused until the player is no longer in Phazon
			end
			
			if P_PlayerInPain(player) == true
			and player.mo.pzpaintime ~= 0 then
				player.mo.pzpaintime = $ + 2 --safeguard against instantly dying upon getting smacked into Phazon
			end
		end
	end
end, "PZPAIN")

//Hard to Kill - Minecart Crash (Code to survive crashing a minecart outside of designated minecart stoppers at the expense of 25% energy)
addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "annie_belnades" then
			return
		end
	end
end)

//Annie can't survive death pits regardless of how much energy she has. Well, technically, she MIGHT be able to survive, but she'd have no feasible way to get back to the level after falling that far, so she might as well be flagged as dead anyways.

//Annie also can't survive failing a level (e.g. a level timer expiring, losing a Metal Sonic or Competition race, etc.), as that's not her literally dying, that's the game progressing from a state where the player has failed.