freeslot(
"S_PLAY_CASSANDRA_CRAWLACRAWL",
"S_PLAY_CASSANDRA_SDURFJUMP",
"S_PLAY_CASSANDRA_BUZZFLIGHT",
"S_PLAY_CASSANDRA_SPRINGSHELLARMOR",
"S_PLAY_CASSANDRA_TURRETFIRE",
"S_PLAY_CASSANDRA_POPUPTURRETFIRE",
"S_PLAY_CASSANDRA_DETONBLAST",
"S_PLAY_CASSANDRA_CRUSHSTACEANPUNCH",
"S_PLAY_CASSANDRA_BANPYURASPRING",
"S_PLAY_CASSANDRA_ROBOHOODARROW",
"S_PLAY_CASSANDRA_ROBOHOODJUMP",
"S_PLAY_CASSANDRA_LANCEABOTCHARGE",
"S_PLAY_CASSANDRA_EGGGUARDSHIELD",
"S_PLAY_CASSANDRA_BASHFLIGHT",
"S_PLAY_CASSANDRA_GREENSNAPPERARMOR",
"S_PLAY_CASSANDRA_MINUSBURROW",
"S_PLAY_CASSANDRA_PYREFLYFLAMES",
"S_PLAY_CASSANDRA_PYREFLYFLIGHT",
"S_PLAY_CASSANDRA_DRAGONBOMBERBOMB",
"S_PLAY_CASSANDRA_DRAGONBOMBERFLIGHT",
"S_PLAY_CASSANDRA_SPINCUSHIONSHELL",
"S_PLAY_CASSANDRA_JETTYSYNFIRE",
"S_PLAY_CASSANDRA_JETTYSYNBOMB",
"S_PLAY_CASSANDRA_JETTYSYNFLIGHT",
"S_PLAY_CASSANDRA_PENGUINATORSLIDE",
"S_PLAY_CASSANDRA_SPINBOBERTSKULLSWING",
"S_PLAY_CASSANDRA_SPINBOBERTFLOAT"
)

freeslot(
"MT_CASSANDRA_CRUSHCLAW",
"MT_CASSANDRA_EGGSHIELD", //An alternative object to the default MT_EGGSHIELD used to modify its behavior
"MT_CASSANDRA_BUMBLEBORE" //An alternative object to the default MT_BUMBLEBORE used to modify its behavior
)

freeslot(
"S_CASSANDRA_EGGSHIELD",
"S_CASSANDRA_BUMBLEBORE_SPAWN",
"S_CASSANDRA_BUMBLEBORE_LOOK1",
"S_CASSANDRA_BUMBLEBORE_LOOK2",
"S_CASSANDRA_BUMBLEBORE_FLY1",
"S_CASSANDRA_BUMBLEBORE_FLY2",
"S_CASSANDRA_BUMBLEBORE_RAISE",
"S_CASSANDRA_BUMBLEBORE_FALL1",
"S_CASSANDRA_BUMBLEBORE_FALL2",
"S_CASSANDRA_BUMBLEBORE_STUCK1",
"S_CASSANDRA_BUMBLEBORE_STUCK2",
"S_CASSANDRA_BUMBLEBORE_DIE"
)

addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "cassandra_b" then
			return
		end

		if player.mo.copypoweruse == nil then
			player.mo.copypoweruse = 0 --initialize copypoweruse variable
		end
		
		if player.mo.copypowerjump == nil then
			player.mo.copypowerjump = 0
		end
		
		if P_IsObjectOnGround(player.mo) == true then
			player.mo.copypowerjump = 0 --reset copy power jump (even if power was discarded during jump)
		end
		
		if player.mo.copyduration == nil then
			player.mo.copyduration = 0
		end
		
		if player.mo.copy ~= 0 then
			if (leveltime%TICRATE) then
				player.mo.copyduration = $ + 1 --track how long Cassandra has had her current power (mostly so that objects can be spawned and tutorials can be given on frame 1)
			end
		end
		
		if player.extrahit == nil then
			player.extrahit = 0
		end
		
		if player.extrahit > 0 then
			if (leveltime%TICRATE) then
				player.extrahit = $ - 1 --timer for certain sturdiness-related copy abilities
			end
		end
		
		if player.mo.selfexplosion == nil then
			player.mo.selfexplosion = 0
		end
		
		if player.mo.copy == 0 --remove stat modifications caused by copying when copy power is discarded
		and player.mo.mimicry == 0 then --ditto for mimicry
			player.mo.copypoweruse = 0 --obviously, a continuous copy ability is not in use when no copy power is held
			player.mo.copyduration = 0 --reset copy ability duration tracker
			player.extrahit = 0
			player.height = skins[player.skin].height --restore normal stats
			player.radius = skins[player.skin].radius
			player.spinheight = skins[player.skin].spinheight
			player.camerascale = skins[player.skin].camerascale
			player.shieldscale = skins[player.skin].shieldscale
			player.mindash = skins[player.skin].mindash
			player.maxdash = skins[player.skin].maxdash
			player.normalspeed = skins[player.skin].normalspeed
			player.actionspd = skins[player.skin].actionspd
			player.acceleration = skins[player.skin].acceleration
			player.accelstart = skins[player.skin].accelstart
			player.thrustfactor = skins[player.skin].thrustfactor
			player.runspeed = skins[player.skin].runspeed
			player.jumpfactor = skins[player.skin].jumpfactor
			player.charability = skins[player.skin].ability
			player.charability2 = skins[player.skin].ability2
			player.charflags = skins[player.skin].flags
			player.followitem = skins[player.skin].followitem --return default followitem
			player.thokitem = skins[player.skin].thokitem --return default thokitem
			player.spinitem = skins[player.skin].spinitem
			player.revitem = skins[player.skin].revitem
		end
	end
end)

addHook("PlayerSpawn", function(player)
	if player.mo and player.mo.valid then
		if player.mo.skin ~= "cassandra_b" then
			return
		end
		
		player.mo.selfexplosion = 0 --reset self-explosion count
	end
end)

mobjinfo[MT_CASSANDRA_EGGSHIELD] = {
	spawnstate = S_EGGSHIELD,
	seestate = S_EGGSHIELD,
	deathstate = S_XPLD1,
	attacksound = sfx_s3k7b,
	deathsound = sfx_wbreak,
	spawnhealth = 1,
	reactiontime = 35,
	speed = 3,
	radius = 36*FRACUNIT, --Cassandra's Egg Shield is wider than the original in order to cover her whole body.
	height = 128*FRACUNIT,
	mass = 100,
	damage = 0,
	flags = MF_SPECIAL|MF_NOGRAVITY
}

addHook("PlayerThink", function(player)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "cassandra_b" then
			return
		end

		if player.mo.copy == MT_BLUECRAWLA or player.mo.copy == MT_REDCRAWLA then --Crawla crawl
			if player.mo.copyduration == 1 then
				print("You copied a Crawla! Press and hold C1 to crawl, reducing your height and enhancing your Spin-Dash! However, your running speed will be reduced...")
			end
			if player.custom1down > 1
			and player.mo.energy >= 1*FRACUNIT/2 then
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 1*FRACUNIT/2
				end
				player.mo.copypoweruse = 1 --prevent energy regeneration while (admittely lame) power is in use
				player.height = 32*FRACUNIT --lower height
				player.spinheight = 21*FRACUNIT
				player.mindash = 20*FRACUNIT --higher minimum Spin-dash charge
				player.normalspeed = 32*FRACUNIT --lower running speed
				player.runspeed = 26*FRACUNIT --allow run animation to still play anyways
			end
			if (player.custom1down == 0
			or player.mo.energy < 1*FRACUNIT/2) --end effect when custom1 is released or energy runs out
			or player.tossflagdown == 1 then --also end effect if power is discarded
				player.mo.copypoweruse = 0
				player.height = skins[player.skin].height --restore normal stats
				player.spinheight = skins[player.skin].spinheight
				player.mindash = skins[player.skin].mindash
				player.normalspeed = skins[player.skin].normalspeed
				player.runspeed = skins[player.skin].runspeed
			end
		end

		if player.mo.copy == MT_GFZFISH then --SDURF high jump
			if player.mo.copyduration == 1 then
				print("You copied a SDURF! Press C1 to perform a super-high jump!")
			end
			if player.custom1down == 1
			and player.mo.copypowerjump == 0
			and player.mo.energy >= 10*FRACUNIT then
				player.mo.energy = $ - 10*FRACUNIT
				P_SetObjectMomZ(player.mo, 50*FRACUNIT, true) --high jump
				player.mo.copypowerjump = 1 --only once per airtime
			end
		end

		if player.mo.copy == MT_GOLDBUZZ or player.mo.copy == MT_REDBUZZ then --Buzz flight
			if player.mo.copyduration == 1 then
				print("You copied a Buzz! Press and hold C1 to fly! While flying, hold Jump to ascend or Spin to descend. You won't be moving particularly fast, though...")
			end
			if player.custom1down > 1
			and player.mo.energy >= 9*FRACUNIT/2
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during Buzz flight
			end
			if player.custom1down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 9*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Buzz
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 9*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, 0, false) --player hovers in the air
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 9*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Buzz
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 9*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, 4*FRACUNIT, false) --player slowly ascends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 9*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Buzz
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 9*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, -4*FRACUNIT, false) --player slowly descends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down == 0 --if the player is not flying
			or player.mo.energy < 9*FRACUNIT/2 then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
		end
		
		if player.mo.copy == MT_SPRINGSHELL or player.mo.copy == MT_YELLOWSHELL then --Spring Shell's power is better-suited for a ShouldDamage hook, but I still need to give a tutorial here
			if player.mo.copyduration == 1 then
				print("You copied a Spring Shell! Any enemy that attacks you from above will be launched into the sky, leaving you unharmed.","You can't shrug attacks off forever, though...")
				if player.mo.copy == MT_YELLOWSHELL then
					print("No, seriously, anything that attacks you from above is taking a ride into the stratosphere.")
				end
			end
		end
		
		if player.mo.turretcooldown == nil then
			player.mo.turretcooldown = 0
		end
		
		if player.mo.turretcooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.turretcooldown = $ - 1
			end
			S_StartSound(player.mo, sfx_trpowr, player) --play a sound to let the player know they're on cooldown
		else
			if player.mo.energy >= 6*FRACUNIT then
				S_StopSoundByID(player.mo, sfx_trpowr) --stop the cooldown sound when the next shot is ready
			end
		end
		
		if player.mo.copy == MT_POPUPTURRET then
			if player.mo.copyduration == 1 then
				print("You copied a pop-up turret! Press C1 to shoot an energy bullet. The cooldown period can be erratic, though...")
			end
			if player.custom1down == 1
			and player.mo.energy >= 6*FRACUNIT
			and player.mo.turretcooldown == 0 then
				player.mo.energy = $ - 6*FRACUNIT --spend energy
				S_StartSound(player.mo, sfx_trfire, nil) --play the turret shooting sound
				P_SpawnPlayerMissile(player.mo, MT_JETTBULLET, 0) --shoot a bullet
				player.mo.turretcooldown = P_RandomKey(5*TICRATE) --random-length cooldown (I would like to weigh this distribution to favor values closer to 69 (the default ReactionTime of MT_POPUPTURRET if its Thing's Angle is 0 or less), but I'm not sure how to do that)
			end
		end
		
		if player.mo.copy == MT_TURRET then
			if player.mo.copyduration == 1 then
				print("You copied a super turret! Press and hold C1 to rapidly shoot energy bullets! You will need to warm up for a little while before you can open fire.")
			end
			if player.custom1down > 0 and player.custom1down < 24 --If the player is warming up
			and player.mo.energy >= 3*FRACUNIT then
				S_StartSound(player.mo, sfx_trpowr, nil) --Play the turret charging sound
			end
			if player.custom1down >= 24 then --Require a warm-up period
				if (player.custom1down%4) --This IS a machine gun; let's have it trigger once every 4 tics while C1 is held
				and player.mo.energy >= 3*FRACUNIT then
					S_StopSoundByID(player.mo, sfx_trpowr) --stop the turret warm-up sound
					player.mo.energy = $ - 3*FRACUNIT --spend energy per shot
					S_StartSound(player.mo, sfx_trfire, nil) --play the turret shooting sound
					P_SpawnPlayerMissile(player.mo, MT_TURRETLASER, 0) --shoot lasers
				end
			end
			if player.custom1down == 0 then
				S_StopSoundByID(player.mo, sfx_trpowr)
				S_StopSoundByID(player.mo, sfx_trfire)
			end
		end
		
		if player.mo.copy == MT_DETON then --Deton explosion
			if player.mo.copyduration == 1 then
				print("You copied a Deton! Press C1 to blow apart everything in your vicinity... including yourself.")
			end
			if player.custom1down == 1
			and player.mo.energy >= 25*FRACUNIT then
				player.mo.energy = $ - 25*FRACUNIT
				P_NukeEnemies(player.mo, player.mo, 2000*FRACUNIT) --Self-detonate and blast enemies
				P_DamageMobj(player.mo, nil, nil, 1) --You just blew yourself up; you're getting hurt, too
				player.mo.selfexplosion = $ + 1 --count how many times the player explodes themself
			end
		end
		
		if player.mo.copy == MT_BIGMINE then --Big Floating Mine explosion (this counts as a Badnik?!)
			if player.mo.copyduration == 1 then
				print("You copied a Big Floating Mine! Press C1 to blow apart everything in your vicinity... including yourself.")
			end
			if player.custom1down == 1
			and player.mo.energy >= 25*FRACUNIT then
				player.mo.energy = $ - 25*FRACUNIT
				P_NukeEnemies(player.mo, player.mo, 2000*FRACUNIT) --Self-detonate and blast enemies
				P_DamageMobj(player.mo, nil, nil, 1) --You just blew yourself up; you're getting hurt, too
				player.mo.selfexplosion = $ + 1 --count how many times the player explodes themself
			end
		end
		
		if player.mo.minecooldown == nil then
			player.mo.minecooldown = 0
		end
		
		if player.mo.minecooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.minecooldown = $ - 1
			end
		end
		
		if player.mo.standingonwater == nil then
			player.mo.standingonwater = 0
		end
		
		if player.mo.copy == MT_SKIM then
			if player.mo.copyduration == 1 then
				print("You copied a Skim! You can stand and walk on the surface of water - press C1 to submerge! You can also drop bombs by pressing C2!")
			end
			if player.mo.standingonwater == 0
			and player.mo.energy >= 2*FRACUNIT
			and (player.eflags & MFE_TOUCHWATER) then
				player.mo.standingonwater = 1
			end
			if player.custom2down == 1
			and player.mo.energy >= 7*FRACUNIT/2
			and player.mo.minecooldown == 0 then
				player.mo.energy = $ - 7*FRACUNIT/2
				S_StartSound(player.mo, sfx_s3k51, nil)
				if P_IsObjectOnGround(player.mo) == true then --dropping mines on the ground is just stupid
					if player.mo.standingonwater ~= 1 then --if the ground the player is standing on isn't a water surface
						P_Earthquake(player.mo, player.mo, 50*FRACUNIT) --small localized explosion
						if player.powers[pw_invulnerability] == 0 and player.powers[pw_flashing] == 0 and player.powers[pw_super] == 0 then --if the player is susceptible to damage
							P_DoPlayerPain(player, nil, nil) --you blew yourself up, dingus
							player.mo.selfexplosion = $ + 1 --count how many times the player explodes themself
							if not (player.mo.selfexplosion%5) then --every 5 self-detonations
								player.timeshit = $ + 1 --count this as damage towards timeshit
								P_PlayerFlagBurst(player, false)
								P_PlayerEmeraldBurst(player, false)
							end
							if (player.powers[pw_shield] & SH_FORCEHP) then --if the player has a Force Shield with more than 1 HP
								if P_RandomChance(FRACUNIT/10) == true then --since this is minor damage, only damage the shield 10% of the time
									target.player.powers[pw_shield] = SH_FORCE --Damage the Force Shield
									P_SpawnShieldOrb(player) --make sure that the proper Force Shield sprite is shown
								end
								//return --stop running the hook so it doesn't completely kill the shield
							elseif 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
								if P_RandomChance(FRACUNIT/10) == true then --since this is minor damage, only damage the shield 10% of the time
									P_RemoveShield(player) --remove the shield
									S_StartSound(player.mo, sfx_s3k35, nil)
								end
								//return
							elseif player.powers[pw_shield] == 0 and player.rings >= 5 then
								P_PlayerRingBurst(player, 5)
								player.rings = $ - 5 --lose 5 Rings
								S_StartSound(player.mo, sfx_s3kb9, nil)
								//return
							elseif player.powers[pw_shield] == 0 and player.rings > 0 and player.rings < 5 then
								P_PlayerRingBurst(player, player.rings)
								player.rings = 0
								S_StartSound(player.mo, sfx_s3kb9, nil)
								//return --stop running this hook before it proceeds to the 0 Rings part and runs P_KillMobj
							elseif player.powers[pw_shield] == 0 and player.rings == 0 then
								P_KillMobj(player.mo, nil, nil) --Congratulations, you blew yourself up so much that you died!
							end
						end
					else
						local cassandrasmine = P_SPMAngle(player.mo, MT_MINE, player.mo.angle, 0, 0)
						cassandrasmine.target = player.mo
					end
				end
				if P_IsObjectOnGround(player.mo) == false then
					local cassandrasmine = P_SPMAngle(player.mo, MT_MINE, player.mo.angle, 0, 0)
					cassandrasmine.target = player.mo
				end
				player.mo.minecooldown = 3*TICRATE/2
			end
		end
		
		if player.mo.copy ~= MT_SKIM then
			player.mo.standingonwater = 0
		end
		
		if player.mo.punchclock == nil then
			player.mo.punchclock = 0
		end
		
		if player.mo.punchclock > 0 then
			if player.mo.copy == MT_CRUSHSTACEAN then
				player.mo.copypoweruse = 1
				A_CustomPower(player.mo, 29, 5398)
			end
			if (leveltime%TICRATE) then
				player.mo.punchclock = $ - 1
			end
		end
		
		if player.mo.punchclock == 0 then
			if player.mo.copy == MT_CRUSHSTACEAN then
				player.mo.copypoweruse = 0
				A_CustomPower(player.mo, 29, 1)
			end
		end
		
		local clawxfromplayer = cos(player.mo.angle)
		local clawyfromplayer = sin(player.mo.angle)
		
		if player.mo.copy == MT_CRUSHSTACEAN then --Crushstacean punch
			if player.mo.copyduration == 1 then
				print("You copied a Crushtacean! Press C1 to launch your claw forwards, smashing anything in its way (including walls and spikes).")
				local cassandrasclaw = P_SpawnMobjFromMobj(player.mo, 30*clawxfromplayer, 20*clawyfromplayer, 8*FRACUNIT, MT_CRUSHCLAW) --have a Crushstacean claw follow the player
				cassandrasclaw.target = player.mo --prevent the claw from harming the player
				A_SetObjectFlags(cassandrasclaw, 16777216, 1)
				A_SetObjectFlags(cassandrasclaw, 32768, 2)
				player.followitem = MT_CRUSHCHAIN --attach the claw to the player with a chain
			end
			if player.mo.punchclock == 0 then
				A_CapeChase(cassandrasclaw, (((8*FRACUNIT)*65536)+0), (((30*clawxfromplayer)*65536)+(20*clawyfromplayer))) --Keep the claw close to the player when not punching with it
			end
			if player.custom1down == 1 --when custom1 is pressed
			and player.mo.energy >= 6*FRACUNIT --if the player has energy
			and player.mo.punchclock == 0 then --can't punch while punching
				player.mo.energy = $ - 6*FRACUNIT
				S_StartSound(player.mo, sfx_s3k6b, nil)
				player.mo.punchclock = 7*TICRATE --perform Crushstacean rocket punch
			end
			if player.mo.punchclock > 4*TICRATE then
				P_InstaThrust(cassandrasclaw, player.mo.angle, 7*FRACUNIT) --send the claw forward
			end
			if player.mo.punchclock < 3*TICRATE then
				P_InstaThrust(cassandrasclaw, (player.mo.angle+ANGLE_180), 7*FRACUNIT) --reel the claw back
			end
		end
		
		if player.mo.copy ~= MT_CRUSHSTACEAN then
			if cassandrasclaw and cassandrasclaw.valid then
				P_KillMobj(cassandrasclaw, nil, nil) --Destroy the claw if Crushstacean power is discarded
			end
		end
		
		local springxfromplayer = cos(player.mo.angle)
		local springyfromplayer = sin(player.mo.angle)
		
		if player.mo.copy == MT_BANPYURA then --Banpyura springs
			if player.mo.copyduration == 1 then
				print("You copied a Banpyura! Your spring claws will repel anything that gets too close!")
				local cassandrasspring1 = P_SpawnMobjFromMobj(player.mo, 15*springxfromplayer, 10*springyfromplayer, 4*FRACUNIT, MT_BANPSPRING) --have a Banpyura spring follow the player
				cassandrasspring1.target = player.mo --prevent the spring from bouncing the player
				local cassandrasspring2 = P_SpawnMobjFromMobj(player.mo, 15*springxfromplayer, 0*springyfromplayer, 4*FRACUNIT, MT_BANPSPRING)
				cassandrasspring2.target = player.mo
				local cassandrasspring3 = P_SpawnMobjFromMobj(player.mo, 15*springxfromplayer, -10*springyfromplayer, 4*FRACUNIT, MT_BANPSPRING)
				cassandrasspring3.target = player.mo
			end
			if cassandrasspring1 and cassandrasspring1.valid then
				A_CapeChase(cassandrasspring1, (((4*FRACUNIT)*63356)+0), (((15*springxfromplayer)*65536)+(10*springyfromplayer))) --have springs follow player
			end
			if cassandrasspring2 and cassandrasspring2.valid then
				A_CapeChase(cassandrasspring2, (((4*FRACUNIT)*63356)+0), (((15*springxfromplayer)*65536)+(0*springyfromplayer)))
			end
			if cassandrasspring3 and cassandrasspring3.valid then
				A_CapeChase(cassandrasspring3, (((4*FRACUNIT)*63356)+0), (((15*springxfromplayer)*65536)+(-10*springyfromplayer)))
			end
		end
		
		if player.mo.copy ~= MT_BANPYURA then
			if cassandrasspring1 and cassandrasspring1.valid then
				P_KillMobj(cassandrasspring1, nil, nil) --remove springs if Banpyura power is discarded
			end
			if cassandrasspring2 and cassandrasspring2.valid then
				P_KillMobj(cassandrasspring2, nil, nil)
			end
			if cassandrasspring3 and cassandrasspring3.valid then
				P_KillMobj(cassandrasspring3, nil, nil)
			end
		end
		
		if player.mo.arrowcooldown == nil then
			player.mo.arrowcooldown = 0
		end
		
		if player.mo.arrowcooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.arrowcooldown = $ - 1
			end
		end
		
/*		if player.mo.angle == 0 then
			local adjustedplayerangle = 360 --making sure that an angle value of 0 can be properly reversed by multiplying it by -1
		else
			local adjustedplayerangle = player.mo.angle
		end*/
		
		local arrowxfromplayer = sin(player.mo.angle)
		local arrowyfromplayer = cos(player.mo.angle)
		
		if player.mo.copy == MT_ROBOHOOD then --Robo Hood arrows & jumping
			if player.mo.copyduration == 1 then
				print("You copied a Robo Hood! Press C1 to fire an arrow. Hold C1 briefly to fire a longer-ranged arrow. Press C2 or C3 to leap backwards or forwards, respectively.")
			end
			if player.custom1down == 1
			and player.mo.energy >= 24*FRACUNIT/5 --if the player has energy
			and player.mo.arrowcooldown == 0 then --this isn't a machine gun
				player.mo.energy = $ - 24*FRACUNIT/5 --spend energy
				local arrow = P_SpawnPlayerMissile(player.mo, MT_ARROW, 0) --fire an arrow
				P_MoveOrigin(arrow, (arrow.x + 6*arrowxfromplayer), (arrow.y + 6*arrowyfromplayer), (arrow.z + 2*FRACUNIT)) --move the arrow a little further from the player
				P_SetObjectMomZ(arrow, 3*FRACUNIT, true) --give the arrow a bit more height so its flight distance isn't embarrassingly short
				P_Thrust(arrow, player.mo.angle, 6*FRACUNIT) --give the arrow a bit more speed
				player.mo.arrowcooldown = 5*TICRATE/4
			end
			if (arrow and arrow.valid) then
				if player.mo.arrowcooldown > 0 then
					P_SetObjectMomZ(arrow, 2*FRACUNIT, true) --defy gravity just a little
					P_Thrust(arrow, arrow.angle, 4*FRACUNIT) --move a little faster
				else
					P_SetObjectMomZ(arrow, 1*FRACUNIT, true) --defy gravity just a little
					P_Thrust(arrow, arrow.angle, 2*FRACUNIT) --move a little faster
				end
			end
			if player.custom1down == 9*TICRATE/5
			and player.mo.energy >= 93*FRACUNIT/12
			and player.mo.arrowcooldown == 0 then
				player.mo.energy = $ - 93*FRACUNIT/12
				local superarrow = P_SpawnPlayerMissile(player.mo, MT_ARROW, 0) --fire a longer-reaching arrow
				P_MoveOrigin(superarrow, (superarrow.x + 12*arrowxfromplayer), (superarrow.y + 12*arrowyfromplayer), (superarrow.z + 8*FRACUNIT)) --move the arrow a little further from the player
				P_SetObjectMomZ(superarrow, 9*FRACUNIT, true) --give the arrow more height
				P_Thrust(superarrow, player.mo.angle, 12*FRACUNIT) --give the arrow more speed
				player.mo.arrowcooldown = 11*TICRATE/6
			end
			if (superarrow and superarrow.valid) then
				if player.mo.arrowcooldown > 0 then
					P_SetObjectMomZ(superarrow, 6*FRACUNIT, true) --defy gravity just a little
					P_Thrust(superarrow, superarrow.angle, 8*FRACUNIT) --move a little faster
				else
					P_SetObjectMomZ(superarrow, 3*FRACUNIT, true) --defy gravity just a little
					P_Thrust(superarrow, superarrow.angle, 4*FRACUNIT) --move a little faster
				end
			end
			if player.custom2down == 1
			and player.mo.energy >= 13*FRACUNIT/3
			and player.mo.copypowerjump == 0 then --prevent jump spamming
				player.mo.energy = $ - 13*FRACUNIT/3
				if P_IsObjectOnGround(player.mo) == true then
					P_MoveOrigin(player.mo, (player.mo.x + 0), (player.mo.y + 0), (player.mo.z + 2*FRACUNIT)) --move the player into the air so that P_InstaThrust doesn't cut itself short
					P_InstaThrust(player.mo, (player.mo.angle+ANGLE_180), 30*FRACUNIT) --jump backwards
					P_SetObjectMomZ(player.mo, 17*FRACUNIT/2, true) --jump height is low; it's more of an evasive maneuver or motion cancel than a core platforming technique
				else
					P_InstaThrust(player.mo, (player.mo.angle+ANGLE_180), 25*FRACUNIT) --jump backwards
					P_SetObjectMomZ(player.mo, 13*FRACUNIT/2, true) --jump height is low; it's more of an evasive maneuver or jump cancel than a core platforming technique
				end
				player.pflags = $1|PF_JUMPED --allow this jump to damage enemies if Vixen Vortex wasn't used beforehand
				player.mo.copypowerjump = 1
			end
			if player.custom3down == 1
			and player.mo.energy >= 13*FRACUNIT/3
			and player.mo.copypowerjump == 0 then --prevent jump spamming
				player.mo.energy = $ - 13*FRACUNIT/3
				if P_IsObjectOnGround(player.mo) == true then
					P_MoveOrigin(player.mo, (player.mo.x + 0), (player.mo.y + 0), (player.mo.z + 2*FRACUNIT)) --move the player into the air so that P_InstaThrust doesn't cut itself short
					P_InstaThrust(player.mo, player.mo.angle, 30*FRACUNIT) --jump forwards
					P_SetObjectMomZ(player.mo, 17*FRACUNIT/2, true) --jump height is low; it's more of an offensive lunge or burst movement than a core platforming technique
				else
					P_InstaThrust(player.mo, player.mo.angle, 25*FRACUNIT) --jump forwards
					P_SetObjectMomZ(player.mo, 13*FRACUNIT/2, true) --jump height is low; it's more of an offensive lunge or jump extender than a core platforming technique
				end
				player.pflags = $1|PF_JUMPED --allow this jump to damage enemies if Vixen Vortex wasn't used beforehand
				player.mo.copypowerjump = 1
			end
		end
		
		if player.mo.lanceabotdashmodetoggle == nil then
			player.mo.lanceabotdashmodetoggle = 0
		end
		
		if player.mo.lanceabotdashmodetogglecooldown == nil then
			player.mo.lanceabotdashmodetogglecooldown = 0
		end
		
		if player.mo.lanceabotdashmodetogglecooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.lanceabotdashmodetogglecooldown = $ - 1 --Yes, it sucks to have a delay before dashmode can be reactivated, but without the delay, manually deactivating it would require frame-perfect button-pressing.
			end
		end
		
		if player.mo.copy == MT_FACESTABBER then --Lance-a-Bot charge
			if player.mo.copyduration == 1 then
				print("You copied a Lance-a-Bot! Press and hold C1 to gradually gain speed, eventually moving fast enough to ignore damage and smash enemies, spikes, and walls in your path. Press C2 to enable a dashmode for even more speed!","(You can also shrug off one hit with no Rings or shield.)")
			end
			if player.custom1down > 1
			and player.mo.energy >= 5*FRACUNIT then
				player.mo.copypoweruse = 1
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 5*FRACUNIT
					player.normalspeed = $ + 3*FRACUNIT --top speed gradually increases while charging
				end
				P_Thrust(player.mo, player.mo.angle, 3*FRACUNIT) --player charges like Lance-A-Bot
			end
			if player.speed == 50*FRACUNIT then
				S_StartSound(player.mo, sfx_zoom, nil) --Play Lance-a-Bot charging sound upon reaching charge speed
			end
			if player.speed >= 50*FRACUNIT then
				player.charflags = $1|SF_CANBUSTWALLS --player can smash walls by charging
				P_SpawnGhostMobj(player.mo) --ghostmobjs let the player know that they have enough speed to attack
			else
				player.charflags = $&~SF_CANBUSTWALLS
				S_StopSoundByID(player.mo, sfx_zoom)
			end
			if player.custom1down == 0
			or player.mo.energy < 5*FRACUNIT then
				player.normalspeed = skins[player.skin].normalspeed --reset to default top speed when charge ends
			end
			if player.custom2down == 1 --an alternative way to speed up
			and player.mo.lanceabotdashmodetoggle == 0
			and player.mo.lanceabotdashmodetogglecooldown == 0
			and player.mo.energy >= 2*FRACUNIT then
				player.mo.lanceabotdashmodetoggle = 1 --turn on dashmode
				player.mo.lanceabotdashmodetogglecooldown = TICRATE/2
			end
			if (player.custom2down == 1 and player.mo.lanceabotdashmodetoggle == 1 and player.mo.lanceabotdashmodetogglecooldown == 0)
			or (player.mo.energy < 2*FRACUNIT and player.mo.lanceabotdashmodetoggle == 1) then
				player.mo.lanceabotdashmodetoggle = 0 --turn off dashmode
				player.mo.lanceabotdashmodetogglecooldown = TICRATE/2
			end
			if player.mo.lanceabotdashmodetoggle == 1 then
				player.mo.copypoweruse = 1
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 2*FRACUNIT --Lance-a-Bot dashmode costs energy to maintain
				end
				player.charflags = $1|SF_DASHMODE
			else
				player.charflags = $&~SF_DASHMODE
			end
			if (player.custom1down == 0 and player.mo.lanceabotdashmodetoggle == 0)
			or player.mo.energy < 2*FRACUNIT then
				player.mo.copypoweruse = 0
			end
		end
		
		if player.mo.copy ~= MT_FACESTABBER then
			player.mo.lanceabotdashmodetoggle = 0
			player.mo.lanceabotdashmodetogglecooldown = 0
		end
		
		local shieldxfromplayer = cos(player.mo.angle)
		local shieldyfromplayer = sin(player.mo.angle)
		
		if player.mo.eggshieldup == nil then
			player.mo.eggshieldup = 0
		end
		
		if player.mo.eggshieldup > 0 then
			if (leveltime%TICRATE) then
				player.mo.eggshieldup = $ - 1
			end
		end
		
		if player.mo.copy == MT_EGGGUARD then --Egg Guard shield
			if player.mo.copyduration == 1 then
				print("You copied an Egg Guard! Your big shield will deflect attacks from the front. You can also shrug off one hit, but you'll lose the power.")
				local cassandrasshield = P_SpawnMobjFromMobj(player.mo, 75*shieldxfromplayer, 75*shieldyfromplayer, 4*FRACUNIT, MT_CASSANDRA_EGGSHIELD) --give the player an Egg Guard shield
				cassandrasshield.target = player.mo --prevent shield from impeding player's movement
			end
			if cassandrasshield and cassandrasshield.valid then
				A_CapeChase(cassandrasshield, (((4*FRACUNIT)*63356)+0), (((75*FRACUNIT)*63356)+0))
				player.mo.eggshieldup = 2*TICRATE
			else
				if player.custom1down == 1
				and player.mo.energy >= 100*FRACUNIT/3
				and player.mo.eggshieldup == 0 then
					player.mo.energy = $ - 100*FRACUNIT/3
					local cassandrasshield = P_SpawnMobjFromMobj(player.mo, 75*shieldxfromplayer, 75*shieldyfromplayer, 4*FRACUNIT, MT_CASSANDRA_EGGSHIELD) --replace a lost Egg Guard shield
					cassandrasshield.target = player.mo
				end
			end
			player.shieldscale = skins[player.skin].shieldscale + 3*FRACUNIT/10 --enlarge any shield orb the player may have
		end --most of the Egg Guard's ability is best-suited for a ShouldDamage hook
		
		if player.mo.copy ~= MT_EGGGUARD then
			if cassandrasshield and cassandrasshield.valid then
				P_KillMobj(cassandrasshield, nil, nil) --lose the shield with the power
			end
		end
		
		if player.mo.copy == MT_VULTURE then --BASH flight
			if player.mo.copyduration == 1 then
				print("You copied a BASH! Press and hold C1 or C2 to continuously fly forward. C1 will make you accelerate, while C2 will keep your speed constant. Press and hold Jump or Spin while flying to ascend or descend.")
			end
			if player.custom1down > 1
			and player.mo.energy >= 15*FRACUNIT/2
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during BASH flight
			end
			if player.custom1down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 15*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a BASH
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 15*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, 0, false) --player hovers in the air
				P_Thrust(player.mo, player.mo.angle, 6*FRACUNIT) --player flies forward
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 15*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a BASH
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 15*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, 8*FRACUNIT, false) --player slowly ascends
				P_Thrust(player.mo, player.mo.angle, 5*FRACUNIT) --player flies forward
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 15*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a BASH
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 15*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, -8*FRACUNIT, false) --player slowly descends
				P_Thrust(player.mo, player.mo.angle, 5*FRACUNIT) --player flies forward
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down == 0 --if the player is not flying
			or player.mo.energy < 15*FRACUNIT/2 then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
			if player.custom2down > 1 --alternative flight that's less prone to spiral out of control
			and player.mo.energy >= 5*FRACUNIT
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during BASH flight
			end
			if player.custom2down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 15*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a BASH
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 15*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, 0, false) --player hovers in the air
				P_InstaThrust(player.mo, player.mo.angle, 50*FRACUNIT) --player flies forward
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom2down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 15*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a BASH
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 15*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, 6*FRACUNIT, false) --player slowly ascends
				P_InstaThrust(player.mo, player.mo.angle, 45*FRACUNIT) --player flies forward
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom2down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 15*FRACUNIT/2 then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a BASH
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 15*FRACUNIT/2
				end
				P_SetObjectMomZ(player.mo, -6*FRACUNIT, false) --player slowly descends
				P_InstaThrust(player.mo, player.mo.angle, 45*FRACUNIT) --player flies forward
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom2down == 0 --if the player is not flying
			or player.mo.energy < 15*FRACUNIT/2 then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
		end
		
		if player.mo.copy == MT_GSNAPPER then --The Green Snapper's power is best suited to a ShouldDamage hook, but it still needs a tutorial when it's acquired.
			if player.mo.copyduration == 1 then
				print("You copied a Green Snapper! Your spikes will injure anything that tries to attack you from below, leaving you unharmed.","You'll still be in trouble if a whole army attacks your feet, though...")
			end
		end
		
		if player.mo.minusdigging == nil then
			player.mo.minusdigging = 0
		end
		
		if player.mo.copy == MT_MINUS then --Minus digging
			if player.mo.copyduration == 1 then
				print("You copied a Minus! Press and hold C1 in the air to dive to the ground and smash bustable floors! Press and hold C1 on the ground to burrow into the ground and avoid danger! While digging, release C1 to pop out of the ground! Mind your energy, you'll have to emerge eventually!")
			end
			if player.custom1down > 1
			and player.custom2down == 0 then
				if P_IsObjectOnGround(player.mo) == false then
					if player.mo.energy >= 5*FRACUNIT/4 then
						if not (leveltime%TICRATE) then
							player.mo.energy = $ - 5*FRACUNIT/4
						end
						player.mo.copypoweruse = 1
						P_SetObjectMomZ(player.mo, -3*FRACUNIT, true) --rapidly descend
						A_CustomPower(player.mo, 29, 6729) --smash enemies, floors, and spikes beneath the player and bounce high off of springs
					else
						player.mo.copypoweruse = 0 --end power use if C1 is released or energy runs out
					end
				end
				if P_IsObjectOnGround(player.mo) == true then
					if (player.mo.floorrover.master.special == 252 or player.mo.floorrover.master.special == 253 or player.mo.floorrover.master.special == 254 or player.mo.floorrover.master.special == 255 or player.mo.floorrover.master.special == 256) then --if the player is on a breakable FOF
						EV_CrumbleChain(player.mo.sector, player.mo.floorrover) --Smash the FOF
					end
					if player.mo.energy >= 6*FRACUNIT then
						if not (leveltime%TICRATE) then
							player.mo.energy = $ - 6*FRACUNIT
						end
						player.mo.copypoweruse = 1
						player.mo.minusdigging = 1 --variable to state that the player is digging
						player.pflags = $&1|PF_JUMPSTASIS --can't jump while digging
						if not (leveltime%15) then
							P_SpawnMobjFromMobj(player.mo, 0, 0, 0, MF_MINUSDIRT) --kick up dirt mounds
						end
						player.height = 2*FRACUNIT --shrink the player's height to provide a tag to identify that the player is actually digging (and further emulate being underground)
						player.spinheight = 1*FRACUNIT
					end
				end
				if P_IsObjectOnGround(player.mo) == true
				and player.mo.minusdigging == 1 --if the player is digging
				and (player.custom1down == 0 or player.mo.energy < 6*FRACUNIT) then --when the player stops digging
					player.mo.copypoweruse = 0
					player.mo.minusdigging = 0 --remove digging flag
					player.height = skins[player.skin].height --return to default height
					player.spinheight = skins[player.skin].spinheight
					player.pflags = $&~PF_JUMPSTASIS --make sure jumpstasis is removed so that the player can jump
					P_SetObjectMomZ(player.mo, 12*FRACUNIT, false) --pop the player into the air
					player.pflags = $1|PF_JUMPED --designate this as a jump so that it can attack enemies and be followed up with Vixen Vortex or a shield action
					A_CustomPower(player.mo, 29, 5151)
					player.mo.copypowerjump = 1 --designate this as a copy power jump
				end
				if player.mo.minusdigging == 1
				and (P_IsObjectOnGround(player.mo) == false --if the player walks off a ledge while digging
				or player.custom2down > 1) then --cancel digging without the jump by pressing C2
					player.mo.minusdigging = 0 --you're not digging anymore!
					player.height = skins[player.skin].height --return to default height
					player.spinheight = skins[player.skin].spinheight
					if P_IsObjectOnGround(player.mo) == true then
						player.pflags = $&~PF_JUMPSTASIS
						player.mo.copypoweruse = 0 --remove copypoweruse flag if digging was canceled with C2
					end
				end
			end
		end
		
		if player.mo.copy ~= MT_MINUS then --stop digging and emerge if Minus power is discarded while underground
			if P_IsObjectOnGround(player.mo) == true
			and player.mo.minusdigging == 1 then --if the player is digging
				player.mo.copypoweruse = 0
				player.mo.minusdigging = 0 --remove digging flag
				player.height = skins[player.skin].height --return to default height
				player.spinheight = skins[player.skin].spinheight
				player.pflags = $&~PF_JUMPSTASIS --make sure jumpstasis is removed so that the player can jump
/*				P_SetObjectMomZ(player.mo, 12*FRACUNIT, false) --pop the player into the air
				player.pflags = $1|PF_JUMPED --designate this as a jump so that it can attack enemies and be followed up with Vixen Vortex or a shield action
				A_CustomPower(player.mo, 29, 5151)
				player.mo.copypowerjump = 1 --designate this as a copy power jump*/
			end
		end
		
		if player.mo.unidusspikeballcount == nil then
			player.mo.unidusspikeballcount = 0
		end
		
		if player.mo.copy == MT_UNIDUS then --Unidus spike balls
			if player.mo.copyduration == 1 then
				print("You copied a Unidus! You have five spikeballs orbiting you that you can throw at enemies by pressing C1. The spikeballs won't replenish on their own; press C2 to respawn your thrown spikeballs, but note that it costs a lot of energy!")
				local cassandrasspikeball1 = P_SpawnMobjFromMobj(player.mo, -16*FRACUNIT, -24*FRACUNIT, 15*FRACUNIT, MT_UNIBALL) --spawn spikeballs
				cassandrasspikeball1.target = player.mo --assign the spikeball to the player
				local cassandrasspikeball2 = P_SpawnMobjFromMobj(player.mo, 16*FRACUNIT, 24*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
				cassandrasspikeball2.target = player.mo
				local cassandrasspikeball3 = P_SpawnMobjFromMobj(player.mo, 24*FRACUNIT, 16*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
				cassandrasspikeball3.target = player.mo
				local cassandrasspikeball4 = P_SpawnMobjFromMobj(player.mo, -24*FRACUNIT, -16*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
				cassandrasspikeball4.target = player.mo
				local cassandrasspikeball5 = P_SpawnMobjFromMobj(player.mo, 24*FRACUNIT, -16*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
				cassandrasspikeball5.target = player.mo
				player.mo.unidusspikeballcount = 5
			end
			if cassandrasspikeball1 and cassandrasspikeball1.valid then
				A_UnidusBall(cassandrasspikeball1, 0)
			end
			if cassandrasspikeball2 and cassandrasspikeball2.valid then
				A_UnidusBall(cassandrasspikeball2, 0)
			end
			if cassandrasspikeball3 and cassandrasspikeball3.valid then
				A_UnidusBall(cassandrasspikeball3, 0)
			end
			if cassandrasspikeball4 and cassandrasspikeball4.valid then
				A_UnidusBall(cassandrasspikeball4, 0)
			end
			if cassandrasspikeball5 and cassandrasspikeball5.valid then
				A_UnidusBall(cassandrasspikeball5, 0)
			end
			if player.custom1down == 1
			and player.mo.energy >= 4*FRACUNIT
			and player.mo.unidusspikeballcount > 0 then
				if player.mo.unidusspikeballcount == 5 then
					if (cassandrasspikeball5 and cassandrasspikeball5.valid) then --double-checking validity
						player.mo.target = P_LookForEnemies(player, false, true) --selecting a target for A_UnidusBall
						if (player.mo.target and player.mo.target.valid) then --if a target was found
							if P_CheckSight(player.mo, player.mo.target) == true then --if the player can actually see the target
								if abs(player.mo.angle - player.mo.target.angle)*FRACUNIT < 3*cassandrasspikeball5.speed --if A_UnidusBall's conditions for throwing are met
									player.mo.energy = $ - 4*FRACUNIT --energy cost
									A_UnidusBall(cassandrasspikeball5, 1) --throw a spikeball
								end
							end
						end
					end
				end
				if player.mo.unidusspikeballcount == 4 then
					if (cassandrasspikeball4 and cassandrasspikeball4.valid) then --double-checking validity
						player.mo.target = P_LookForEnemies(player, false, true) --selecting a target for A_UnidusBall
						if (player.mo.target and player.mo.target.valid) then --if a target was found
							if P_CheckSight(player.mo, player.mo.target) == true then --if the player can actually see the target
								if abs(player.mo.angle - player.mo.target.angle)*FRACUNIT < 3*cassandrasspikeball4.speed --if A_UnidusBall's conditions for throwing are met
									player.mo.energy = $ - 4*FRACUNIT --energy cost
									A_UnidusBall(cassandrasspikeball4, 1) --throw a spikeball
								end
							end
						end
					end
				end
				if player.mo.unidusspikeballcount == 3 then
					if (cassandrasspikeball3 and cassandrasspikeball3.valid) then--double-checking validity
						player.mo.target = P_LookForEnemies(player, false, true) --selecting a target for A_UnidusBall
						if (player.mo.target and player.mo.target.valid) then --if a target was found
							if P_CheckSight(player.mo, player.mo.target) == true then --if the player can actually see the target
								if abs(player.mo.angle - player.mo.target.angle)*FRACUNIT < 3*cassandrasspikeball3.speed --if A_UnidusBall's conditions for throwing are met
									player.mo.energy = $ - 4*FRACUNIT --energy cost
									A_UnidusBall(cassandrasspikeball3, 1) --throw a spikeball
								end
							end
						end
					end
				end
				if player.mo.unidusspikeballcount == 2 then
					if (cassandrasspikeball2 and cassandrasspikeball2.valid) then --double-checking validity
						player.mo.target = P_LookForEnemies(player, false, true) --selecting a target for A_UnidusBall
						if (player.mo.target and player.mo.target.valid) then --if a target was found
							if P_CheckSight(player.mo, player.mo.target) == true then --if the player can actually see the target
								if abs(player.mo.angle - player.mo.target.angle)*FRACUNIT < 3*cassandrasspikeball2.speed --if A_UnidusBall's conditions for throwing are met
									player.mo.energy = $ - 4*FRACUNIT --energy cost
									A_UnidusBall(cassandrasspikeball2, 1) --throw a spikeball
								end
							end
						end
					end
				end
				if player.mo.unidusspikeballcount == 1 then
					if (cassandrasspikeball1 and cassandrasspikeball1.valid) then --double-checking validity
						player.mo.target = P_LookForEnemies(player, false, true) --selecting a target for A_UnidusBall
						if (player.mo.target and player.mo.target.valid) then --if a target was found
							if P_CheckSight(player.mo, player.mo.target) == true then --if the player can actually see the target
								if abs(player.mo.angle - player.mo.target.angle)*FRACUNIT < 3*cassandrasspikeball1.speed --if A_UnidusBall's conditions for throwing are met
									player.mo.energy = $ - 4*FRACUNIT --energy cost
									A_UnidusBall(cassandrasspikeball1, 1) --throw a spikeball
								end
							end
						end
					end
				end
			end
			if player.custom2down == 1
			and player.mo.energy >= 20*FRACUNIT
			and player.mo.unidusspikeballcount < 5 then
				if player.mo.unidusspikeballcount == 0 then
					player.mo.energy = $ - 20*FRACUNIT
					local cassandrasspikeball1 = P_SpawnMobjFromMobj(player.mo, -16*FRACUNIT, -24*FRACUNIT, 15*FRACUNIT, MT_UNIBALL) --respawn a spikeball
					cassandrasspikeball1.target = player.mo
				end
				if player.mo.unidusspikeballcount == 1 then
					player.mo.energy = $ - 20*FRACUNIT
					local cassandrasspikeball2 = P_SpawnMobjFromMobj(player.mo, 16*FRACUNIT, 24*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
					cassandrasspikeball2.target = player.mo
				end
				if player.mo.unidusspikeballcount == 2 then
					player.mo.energy = $ - 20*FRACUNIT
					local cassandrasspikeball3 = P_SpawnMobjFromMobj(player.mo, 24*FRACUNIT, 16*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
					cassandrasspikeball3.target = player.mo
				end
				if player.mo.unidusspikeballcount == 3 then
					player.mo.energy = $ - 20*FRACUNIT
					local cassandrasspikeball4 = P_SpawnMobjFromMobj(player.mo, -24*FRACUNIT, -16*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
					cassandrasspikeball4.target = player.mo
				end
				if player.mo.unidusspikeballcount == 4 then
					player.mo.energy = $ - 20*FRACUNIT
					local cassandrasspikeball5 = P_SpawnMobjFromMobj(player.mo, 24*FRACUNIT, -16*FRACUNIT, 15*FRACUNIT, MT_UNIBALL)
					cassandrasspikeball5.target = player.mo
				end
			end
			if player.custom3down > 1
			and player.mo.energy >= (player.mo.unidusspikeballcount*FRACUNIT)/2 --energy cost of revving spikeballs depends on how many there are
			and player.mo.unidusspikeballcount > 0 then
				player.mo.copypoweruse = 1
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - (player.mo.unidusspikeballcount*FRACUNIT)/2
					if (cassandrasspikeball5 and cassandrasspikeball5.valid) then
						cassandrasspikeball5.speed = $ + 2*FRACUNIT --rev up spikeballs to make them easier to aim
					end
					if (cassandrasspikeball4 and cassandrasspikeball4.valid) then
						cassandrasspikeball4.speed = $ + 2*FRACUNIT
					end
					if (cassandrasspikeball3 and cassandrasspikeball3.valid) then
						cassandrasspikeball3.speed = $ + 2*FRACUNIT
					end
					if (cassandrasspikeball2 and cassandrasspikeball2.valid) then
						cassandrasspikeball2.speed = $ + 2*FRACUNIT
					end
					if (cassandrasspikeball1 and cassandrasspikeball1.valid) then
						cassandrasspikeball1.speed = $ + 2*FRACUNIT
					end
				end
			end
			if player.custom3down == 0
			or player.mo.energy < (player.mo.unidusspikeballcount*FRACUNIT)/2
			or player.mo.unidusspikeballcount == 0 then
				player.mo.copypoweruse = 0
				if not (leveltime%TICRATE) then
					if (cassandrasspikeball5 and cassandrasspikeball5.valid)
					and cassandrasspikeball5.speed > 2*FRACUNIT then
						cassandrasspikeball5.speed = $ - 2*FRACUNIT --spikeballs gradually fall back down to default speed if not revved
					end
					if (cassandrasspikeball4 and cassandrasspikeball4.valid)
					and cassandrasspikeball4.speed > 2*FRACUNIT then
						cassandrasspikeball4.speed = $ - 2*FRACUNIT
					end
					if (cassandrasspikeball3 and cassandrasspikeball3.valid)
					and cassandrasspikeball3.speed > 2*FRACUNIT then
						cassandrasspikeball3.speed = $ - 2*FRACUNIT
					end
					if (cassandrasspikeball2 and cassandrasspikeball2.valid)
					and cassandrasspikeball2.speed > 2*FRACUNIT then
						cassandrasspikeball2.speed = $ - 2*FRACUNIT
					end
					if (cassandrasspikeball1 and cassandrasspikeball1.valid)
					and cassandrasspikeball1.speed > 2*FRACUNIT then
						cassandrasspikeball1.speed = $ - 2*FRACUNIT
					end
				end
			end
		end
		
		if player.mo.copy ~= MT_UNIDUS then --get rid of remaining spikeballs if copy ability is discarded
			if cassandrasspikeball1 and cassandrasspikeball1.valid then
				P_KillMobj(cassandrasspikeball1, nil, nil)
			end
			if cassandrasspikeball2 and cassandrasspikeball2.valid then
				P_KillMobj(cassandrasspikeball2, nil, nil)
			end
			if cassandrasspikeball3 and cassandrasspikeball3.valid then
				P_KillMobj(cassandrasspikeball3, nil, nil)
			end
			if cassandrasspikeball4 and cassandrasspikeball4.valid then
				P_KillMobj(cassandrasspikeball4, nil, nil)
			end
			if cassandrasspikeball5 and cassandrasspikeball5.valid then
				P_KillMobj(cassandrasspikeball5, nil, nil)
			end
			player.mo.unidusspikeballcount = 0
		end
		
		if player.mo.copy == MT_PYREFLY then --Pyre Fly flames & flight
			if player.mo.copyduration == 1 then
				print("You copied a Pyre Fly! Press and hold C1 to surround yourself in flames. Press C2 to gain immunity to fire. Press and hold C3 to fly (your altitude will be somewhat unstable, though).","Be aware that once you have obtained a Flame Shield, you can't get another for 30 seconds, so try not to lose it!","While flying, hold Jump to ascend and Spin to descend (but be aware that it won't be particularly smooth).")
			end
			if player.custom1down > 1
			and player.mo.energy >= 6*FRACUNIT then
				if player.customdown1 == 2 then
					player.mo.energy = $ - 6*FRACUNIT --up-front energy cost
				end
				if player.custom1down > 40 then
					if not (leveltime%TICRATE) then
						player.mo.energy = $ - 6*FRACUNIT --energy drain per second (skipping the first second since that was accounted for with the initial up-front cost)
					end
				end
				P_SPMAngle(player.mo, MT_FLAMEJETFLAME, FixedAngle(P_RandomRange(0, 360)*FRACUNIT), 0, 0) --haphazardly throw fire everywhere
			end
			if player.custom2down == 1
			and player.extrahit == 0
			and player.mo.energy >= 40*FRACUNIT
			and player.powers[pw_shield] ~= SH_FLAMEAURA then
				player.mo.energy = $ - 40*FRACUNIT
				player.powers[pw_shield] = SH_FLAMEAURA
				//A_CustomPower(player.mo, 3, 1025) --get Flame Shield
				P_SpawnShieldOrb(player)
				print("Flame Shield activated! Fire can't hurt you now!")
				player.extrahit = 30*TICRATE --30-second cooldown
			end
			if player.custom3down > 1
			and player.mo.energy >= 4*FRACUNIT
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during Pyre Fly flight
			end
			if player.custom3down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 4*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Pyre Fly
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 4*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, (P_RandomRange(-2, 2)*FRACUNIT), false) --player erratically hovers in the air
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom3down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 4*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Pyre Fly
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 4*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, (P_RandomRange(0, 4)*FRACUNIT), false) --player slowly and erratically ascends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom3down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 4*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Pyre Fly
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 4*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, (P_RandomRange(-4, 0)*FRACUNIT), false) --player slowly and erratically descends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom3down == 0 --if the player is not flying
			or player.mo.energy < 4*FRACUNIT then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
		end
		
		if player.mo.dragonbombcooldown == nil then
			player.mo.dragonbombcooldown = 0
		end
		
		if player.mo.dragonbombcooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.dragonbombcooldown = $ - 1
			end
		end
		
		if player.mo.dragonbomberundulation == nil then
			player.mo.dragonbomberundulation = 0
		end
		
		if player.mo.dragonbomberundulationflip == nil then
			player.mo.dragonbomberundulationflip = 0
		end
		
		if player.mo.dragonbomberundulation >= 4*FRACUNIT then
			player.mo.dragonbomberundulationflip = 1
		end
		
		if player.mo.dragonbomberundulation <= -4*FRACUNIT then
			player.mo.dragonbomberundulationflip = 0
		end
		
		local dragonbombxfromplayer = cos(player.mo.angle)
		local dragonbombyfromplayer = sin(player.mo.angle)
		
		if player.mo.copy == MT_DRAGONBOMBER then --Dragonbomber bombs, flames, & flight
			if player.mo.copyduration == 1 then
				print("You copied a Dragonbomber! Press C1 to drop a bomb! Press C2 to release fireballs at your sides! Press and hold C3 to fly, but note that your altitude will constantly rise and fall.","While flying, hold Jump to ascend and Spin to descend (but be aware that it won't be particularly smooth).")
			end
			if player.custom1down == 1
			and player.mo.energy >= 13*FRACUNIT/2
			and player.mo.dragonbombcooldown == 0 then
				player.mo.energy = $ - 13*FRACUNIT/2
				if P_IsObjectOnGround(player.mo) == true then --lob bomb on ground
					local cassandrasdragonbomb = P_SpawnMobjFromMobj(player.mo, 80*dragonbombxfromplayer, 80*dragonbombyfromplayer, 60*FRACUNIT, MT_DRAGONMINE)
					cassandrasdragonbomb.target = player.mo
					A_SetObjectFlags(cassandrasdragonbomb, 16777216, 1)
					A_SetObjectFlags(cassandrasdragonbomb, 32768, 2)
					P_SetObjectMomZ(cassandrasdragonbomb, 15*FRACUNIT, false)
					P_InstaThrust(cassandrasdragonbomb, player.mo.angle, 20*FRACUNIT)
					player.mo.dragonbombcooldown = 2*TICRATE
				end
				if P_IsObjectOnGround(player.mo) == false then --drop bombs in the air
					local cassandrasdragonbomb = P_SpawnMobjFromMobj(player.mo, -40*dragonbombxfromplayer, -40*dragonbombyfromplayer, -60*FRACUNIT, MT_DRAGONMINE)
					cassandrasdragonbomb.target = player.mo
					A_SetObjectFlags(cassandrasdragonbomb, 16777216, 1)
					A_SetObjectFlags(cassandrasdragonbomb, 32768, 2)
					player.mo.dragonbombcooldown = TICRATE/2
				end
			end
			if player.custom2down == 1
			and player.mo.energy >= 6*FRACUNIT
			and player.mo.dragonbombcooldown == 0 then
				player.mo.energy = $ - 6*FRACUNIT
				local cassandrasdragonflame1 = P_SPMAngle(player.mo, MT_FLAMEJETFLAME, FixedAngle(AngleFixed(player.mo.angle) + 90*FRACUNIT), 0, 0) --fireball to the left
				cassandrasdragonflame1.target = player.mo
				local cassandrasdragonflame2 = P_SPMAngle(player.mo, MT_FLAMEJETFLAME, FixedAngle(AngleFixed(player.mo.angle) + 270*FRACUNIT), 0, 0) --fireball to the right
				cassandrasdragonflame2.target = player.mo
			end
			if player.custom3down > 1 then
				if (leveltime%TICRATE) then
					if player.mo.dragonbomberundulationflip == 0 then
						player.mo.dragonbomberundulation = $ + 1*FRACUNIT/2 --undulation ascends
					end
					if player.mo.dragonbomberundulationflip == 1 then
						player.mo.dragonbomberundulation = $ - 1*FRACUNIT/2 --undulation descends
					end
				end
			end
			if player.custom3down > 1
			and player.mo.energy >= 4*FRACUNIT
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during Dragonbomber flight
			end
			if player.custom3down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 4*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Dragonbomber
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 4*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, player.mo.dragonbomberundulation, false) --player bobs up and down in the air
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom3down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 4*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Dragonbomber
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 4*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, (player.mo.dragonbomberundulation+(4*FRACUNIT)), false) --player ascends in a bobbing rythm
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom3down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 4*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Dragonbomber
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 4*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, (player.mo.dragonbomberundulation-(4*FRACUNIT)), false) --player descends in a bobbing rythm
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom3down == 0 --if the player is not flying
			or player.mo.energy < 4*FRACUNIT then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
		end
		
		if player.mo.copy == MT_JETTBOMBER then --Jetty-Syn Bomber flight & bombs
			if player.mo.copyduration == 1 then
				print("You copied a Jetty-Syn Bomber! Press and hold C1 to fly! Press C2 to drop bombs!","You'll hurt yourself a little if you drop a bomb while you're standing on the ground...")
			end
			if player.custom1down > 1
			and player.mo.energy >= 5*FRACUNIT
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during Jetty-Syn flight
			end
			if player.custom1down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 5*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Jetty-Syn
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 5*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, 0, false) --player hovers in the air
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 5*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Jetty-Syn
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 5*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, 5*FRACUNIT, false) --player slowly ascends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 5*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Jetty-Syn
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 5*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, -5*FRACUNIT, false) --player slowly descends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down == 0 --if the player is not flying
			or player.mo.energy < 5*FRACUNIT then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
			if player.custom2down == 1
			and player.mo.energy >= 7*FRACUNIT/2
			and player.mo.minecooldown == 0 then
				player.mo.energy = $ - 7*FRACUNIT/2
				S_StartSound(player.mo, sfx_s3k51, nil)
				if P_IsObjectOnGround(player.mo) == true then --dropping mines on the ground is just stupid
					P_Earthquake(player.mo, player.mo, 50*FRACUNIT) --small localized explosion
					if player.powers[pw_invulnerability] == 0 and player.powers[pw_flashing] == 0 and player.powers[pw_super] == 0 then --if the player is susceptible to damage
						P_DoPlayerPain(player, nil, nil) --you blew yourself up, dingus
						player.mo.selfexplosion = $ + 1 --count how many times the player explodes themself
						if not (player.mo.selfexplosion%5) then --every 5 self-detonations
							player.timeshit = $ + 1 --count this as damage towards timeshit
							P_PlayerFlagBurst(player, false)
							P_PlayerEmeraldBurst(player, false)
						end
						if (player.powers[pw_shield] & SH_FORCEHP) then --if the player has a Force Shield with more than 1 HP
							if P_RandomChance(FRACUNIT/10) == true then --since this is minor damage, only damage the shield 10% of the time
								target.player.powers[pw_shield] = SH_FORCE --Damage the Force Shield
								P_SpawnShieldOrb(player) --make sure that the proper Force Shield sprite is shown
							end
							//return --stop running the hook so it doesn't completely kill the shield
						elseif 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
							if P_RandomChance(FRACUNIT/10) == true then --since this is minor damage, only damage the shield 10% of the time
								P_RemoveShield(player) --remove the shield
								S_StartSound(player.mo, sfx_s3k35, nil)
							end
							//return
						elseif player.powers[pw_shield] == 0 and player.rings >= 5 then
							P_PlayerRingBurst(player, 5)
							player.rings = $ - 5 --lose 5 Rings
							S_StartSound(player.mo, sfx_s3kb9, nil)
							//return
						elseif player.powers[pw_shield] == 0 and player.rings > 0 and player.rings < 5 then
							P_PlayerRingBurst(player, player.rings)
							player.rings = 0
							S_StartSound(player.mo, sfx_s3kb9, nil)
							//return --stop running this hook before it proceeds to the 0 Rings part and runs P_KillMobj
						elseif player.powers[pw_shield] == 0 and player.rings == 0 then
							P_KillMobj(player.mo, nil, nil) --Congratulations, you blew yourself up so much that you died!
						end
					end
				end
				if P_IsObjectOnGround(player.mo) == false then
					local cassandrasmine = P_SPMAngle(player.mo, MT_MINE, player.mo.angle, 0, 0)
					cassandrasmine.target = player.mo
				end
				player.mo.minecooldown = 3*TICRATE/2
			end
		end
		
		if player.mo.copy ~= MT_SKIM
		and player.mo.copy ~= MT_JETTBOMBER then
			player.mo.minecooldown = 0
		end
		
		if player.mo.gunnercooldown == nil then
			player.mo.gunnercooldown = 0
		end
		
		if player.mo.gunnercooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.gunnercooldown = $ - 1
			end
		end
		
		if player.mo.copy == MT_JETTGUNNER then --Jetty-Syn Gunner flight & shooting
			if player.mo.copyduration == 1 then
				print("You copied a Jetty-Syn Gunner! Press and hold C1 to fly! Press C2 to fire an energy shot!")
			end
			if player.custom1down > 1
			and player.mo.energy >= 5*FRACUNIT
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during Jetty-Syn flight
			end
			if player.custom1down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 5*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Jetty-Syn
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 5*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, 0, false) --player hovers in the air
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 5*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Jetty-Syn
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 5*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, 5*FRACUNIT, false) --player slowly ascends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 5*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Jetty-Syn
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 5*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, -5*FRACUNIT, false) --player slowly descends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom1down == 0 --if the player is not flying
			or player.mo.energy < 5*FRACUNIT then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
			if player.custom2down == 1
			and player.mo.energy >= 6*FRACUNIT
			and player.mo.gunnercooldown == 0 then
				player.mo.energy = $ - 6*FRACUNIT
				S_StartSound(player.mo, sfx_s3k4d, nil)
				P_SpawnPlayerMissile(player.mo, MT_JETTBULLET, 0) --shoot an energy bullet
				player.mo.gunnercooldown = 3*TICRATE/2
			end
		end
		
		if player.mo.copy ~= MT_JETTGUNNER then
			player.mo.gunnercooldown = 0
		end
		
		if player.mo.copy == MT_SPINCUSHION then --Spincushion shell spin
			if player.mo.copyduration == 1 then
				print("You copied a Spincushion! Press C1 to gain temporary invincibility and extra speed!")
			end
			if player.custom1down == 1
			and player.powers[pw_invulnerability] == 0
			and player.mo.energy >= 40*FRACUNIT then
				player.mo.energy = $ - 40*FRACUNIT
				player.powers[pw_invulnerability] = 8*TICRATE --retreat into shell, become invulnerable
				if player.powers[pw_invulnerability] > 0 then
					player.normalspeed = skins[player.skin].normalspeed + 12*FRACUNIT --speed up while using the shell
					player.charflags = $1|SF_CANBUSTWALLS --break walls with the shell
					player.mo.copypoweruse = 1 --prevent energy recharge during invulnerability
				else
					player.normalspeed = skins[player.skin].normalspeed
					player.charflags = $&~SF_CANBUSTWALLS
					player.mo.copypoweruse = 0
				end
			end
		end
		
		if player.mo.lasercooldown == nil then
			player.mo.lasercooldown = 0
		end
		
		if player.mo.lasercooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.lasercooldown = $ - 1
			end
		end
		
		if player.mo.snailerclimbing == nil then
			player.mo.snailerclimbing = 0
		end
		
		if player.mo.snailerwallcrawl == nil then
			player.mo.snailerwallcrawl = 0
		end
		
		if player.consecutivecrawl == nil then
			player.consecutivecrawl = 0
		end
		
		if player.mo.copy == MT_SNAILER then --Snailer lasers & climbing
			if player.mo.copyduration == 1 then
				print("You copied a Snailer! Press C1 to shoot laser rockets. Press and hold C2 to scale walls.")
			end
			//player.mo.snailerwallcrawl = player.consecutivecrawl
			if player.custom1down == 1
			and player.mo.energy >= 4*FRACUNIT
			and player.mo.lasercooldown == 0 then
				player.mo.energy = $ - 4*FRACUNIT
				if player.mo.snailerclimbing == 0 then
					P_SpawnPlayerMissile(player.mo, MT_ROCKET, 0)
				else
					P_SPMAngle(player.mo, MT_ROCKET, (player.mo.angle+ANGLE_180), 1, 0) --shoot away from the object you're climbing
				end
				player.mo.lasercooldown = 3*TICRATE/2
			end
			if player.custom2down > 1
			and player.mo.energy >= 2*FRACUNIT --if the player has energy
			and player.mo.snailerclimbing > 0 then --and the player is touching a wall
				//player.pflags = $1|PF_STASIS --no lateral movement allowed
				player.mo.xmom = 0
				player.mo.ymom = 0
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during Snailer climbing
				end
				if ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
				or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) then --if neither or both of Jump and Spin are being held
					player.mo.flags = $1|MF_NOGRAVITY --player climbs like a Snailer
					if not (leveltime%TICRATE) then
						player.mo.energy = $ - 2*FRACUNIT
					end
					P_SetObjectMomZ(player.mo, ((player.cmd.forwardmove*FRACUNIT)/12), false) --player moves up and down according to forward or back input
					player.mo.copypoweruse = 1 --prevent energy recharge during climbing
				end
				if player.cmd.buttons & BT_JUMP >= 1
				and player.cmd.buttons & BT_SPIN == 0 then --if Jump is being held, but Spin is not
					player.mo.flags = $1|MF_NOGRAVITY --player climbs like a Snailer
					if not (leveltime%TICRATE) then
						player.mo.energy = $ - 2*FRACUNIT
					end
					P_SetObjectMomZ(player.mo, 6*FRACUNIT, false) --player ascends
					player.mo.copypoweruse = 1 --prevent energy recharge during climbing
				end
				if player.cmd.buttons & BT_JUMP == 0
				and player.cmd.buttons & BT_SPIN >= 1 then --if Spin is being held, but Jump is not
					player.mo.flags = $1|MF_NOGRAVITY --player climbs like a Snailer
					if not (leveltime%TICRATE) then
						player.mo.energy = $ - 2*FRACUNIT
					end
					P_SetObjectMomZ(player.mo, (-6*FRACUNIT), false) --player descends
					player.mo.copypoweruse = 1 --prevent energy recharge during climbing
				end
			end
			if player.custom2down == 0
			or player.mo.energy < 2*FRACUNIT then
				player.mo.snailerclimbing = 0
				player.mo.snailerwallcrawl = 0
				//player.pflags = $&~PF_STASIS
				player.mo.flags = $&~MF_NOGRAVITY --stop climbing
				player.mo.copypoweruse = 0 --allow energy to recharge while not climbing
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
		end
		
		if player.mo.copy ~= MT_SNAILER then
			player.mo.snailerclimbing = 0
			player.mo.snailerwallcrawl = 0
			player.mo.consecutivecrawl = 0
		end
		
		if player.mo.penguinslide1 == nil then
			player.mo.penguinslide1 = 0
		end
		
		if player.mo.penguinslide1 > 0 then
			if (leveltime%TICRATE) then
				player.mo.penguinslide1 = $ - 1
			end
		end
		
		if player.mo.penguinslide2 == nil then
			player.mo.penguinslide2 = 0
		end
		
		if player.mo.penguinslide2 > 0 then
			if (leveltime%TICRATE) then
				player.mo.penguinslide2 = $ - 1
			end
		end
		
		if player.mo.penguinslide3 == nil then
			player.mo.penguinslide3 = 0
		end
		
		if player.mo.penguinslide3 > 0 then
			if (leveltime%TICRATE) then
				player.mo.penguinslide3 = $ - 1
			end
		end
		
		if player.mo.copy == MT_PENGUINATOR then --Penguinator slide
			if player.mo.copyduration == 1 then
				print("You copied a Penguinator! Press and hold C1 to gradually increase your speed! Press C2 or C3 to propel yourself forward!")
			end
			if player.custom1down > 1
			and player.mo.energy > 4*FRACUNIT then
				if player.custom1down == 2 then
					player.mo.energy = $ - 4*FRACUNIT
				end
				if player.custom1down > 40 then
					if not (leveltime%TICRATE) then
						player.mo.energy = $ - 4*FRACUNIT
					end
				end
				player.mo.penguinslide1 = $ + 3
			end
			if player.custom2down == 1
			and player.mo.energy >= 8*FRACUNIT then
				player.mo.energy = $ - 8*FRACUNIT
				player.mo.penguinslide2 = $ + 2*TICRATE
			end
			if player.custom3down == 1
			and player.mo.energy >= 12*FRACUNIT then
				player.mo.energy = $ - 12*FRACUNIT
				player.mo.penguinslide3 = $ + 2*TICRATE
			end
			if player.mo.penguinslide1 > 0 then
				player.mo.copypoweruse = 1
			end
			player.normalspeed = skins[player.skin].normalspeed + player.mo.penguinslide1*FRACUNIT/6
			player.actionspd = skins[player.skin].actionspd + player.mo.penguinslide1*FRACUNIT/6
			player.acceleration = skins[player.skin].acceleration + player.mo.penguinslide1*FRACUNIT/6
			player.accelstart = skins[player.skin].accelstart + player.mo.penguinslide1*FRACUNIT/6
			player.thrustfactor = skins[player.skin].thrustfactor + player.mo.penguinslide1*FRACUNIT/6
			player.runspeed = skins[player.skin].runspeed + player.mo.penguinslide1*FRACUNIT/6
			player.jumpfactor = skins[player.skin].jumpfactor + player.mo.penguinslide1*FRACUNIT/120
			if player.mo.penguinslide2 > 0 then
				P_Thrust(player.mo, player.mo.angle, (2*player.mo.penguinslide2)*FRACUNIT)
				player.mo.copypoweruse = 1
			end
			if player.mo.penguinslide3 > 0 then
				P_Move(player.mo, (2*player.mo.penguinslide3))
				P_SpawnGhostMobj(player.mo)
				player.mo.copypoweruse = 1
			end
			if player.mo.penguinslide1 == 0
			and player.mo.penguinslide2 == 0
			and player.mo.penguinslide3 == 0 then
				player.mo.copypoweruse = 0
			end
		end
		
		if player.mo.copy ~= MT_PENGUINATOR then
			player.mo.penguinslide1 = 0
			player.mo.penguinslide2 = 0
			player.mo.penguinslide3 = 0
		end
		
		if player.mo.snowballcooldown == nil then
			player.mo.snowballcooldown = 0
		end
		
		if player.mo.snowballcooldown > 0 then
			if (leveltime%TICRATE) then
				player.mo.snowballcooldown = $ - 1
			end
		end
		
		if player.mo.copy == MT_POPHAT then
			if player.mo.copyduration == 1 then
				print("You copied a Pophat! Press C1 to launch a snowball towards a nearby enemy! Press C2 to dummy-fire a snowball!")
			end
			if player.custom1down == 1
			and player.mo.energy >= 6*FRACUNIT
			and player.mo.snowballcooldown == 0 then
				player.mo.energy = $ - 6*FRACUNIT
				local cassandraslockonsnowball = P_SpawnMissile(player.mo, P_LookForEnemies(player, true, true), MT_POPSHOT) --throw a snowball directly at a nearby enemy or monitor
				cassandraslockonsnowball.target = player.mo
				P_SetObjectMomZ(cassandraslockonsnowball, 3*FRACUNIT, true)
				player.mo.snowballcooldown = 1*TICRATE
			end
			if player.custom2down == 1
			and player.mo.energy >= 6*FRACUNIT
			and player.mo.snowballcooldown == 0 then
				player.mo.energy = $ - 6*FRACUNIT
				local cassandrassnowball = P_SpawnPlayerMissile(player.mo, MT_POPSHOT, 0)
				cassandrassnowball.target = player.mo
				P_Thrust(cassandrassnowball, player.mo.angle, 10*FRACUNIT)
				P_SetObjectMomZ(cassandrassnowball, 15*FRACUNIT, true)
				player.mo.snowballcooldown = 1*TICRATE
			end
		end
		
		if player.mo.copy == MT_SPINBOBERT then
			if player.mo.copyduration == 1 then
				print("You copied a Spinbobert! Press and hold C1 to surround yourself with will-o-wisps! Press and hold C2 to fly!")
			end
			if player.custom2down > 1
			and player.mo.energy >= 3*FRACUNIT
			and P_IsObjectOnGround(player.mo) == false then
				player.pflags = $&~PF_JUMPED --prevent air actions and shield actions during Jetty-Syn flight
			end
			if player.custom2down > 1
			and ((player.cmd.buttons & BT_JUMP == 0 and player.cmd.buttons & BT_SPIN == 0)
			or (player.cmd.buttons & BT_JUMP >= 1 and player.cmd.buttons & BT_SPIN >= 1)) --if neither or both of Jump and Spin are being held
			and player.mo.energy >= 3*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Spinbobert
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 2*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, 0, false) --player hovers in the air
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom2down > 1
			and player.cmd.buttons & BT_JUMP >= 1
			and player.cmd.buttons & BT_SPIN == 0 --if Jump is being held, but Spin is not
			and player.mo.energy >= 3*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Spinbobert
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 3*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, 3*FRACUNIT, false) --player slowly ascends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom2down > 1
			and player.cmd.buttons & BT_JUMP == 0
			and player.cmd.buttons & BT_SPIN >= 1 --if Spin is being held, but Jump is not
			and player.mo.energy >= 3*FRACUNIT then --if the player has energy
				player.mo.flags = $1|MF_NOGRAVITY --player flies like a Spinbobert
				if not (leveltime%TICRATE) then
					player.mo.energy = $ - 3*FRACUNIT
				end
				P_SetObjectMomZ(player.mo, -3*FRACUNIT, false) --player slowly descends
				player.mo.copypoweruse = 1 --prevent energy recharge during flight
			end
			if player.custom2down == 0 --if the player is not flying
			or player.mo.energy < 3*FRACUNIT then --if the player does not have enough energy to fly
				player.mo.flags = $&~MF_NOGRAVITY --stop flying
				player.mo.copypoweruse = 0 --allow energy to recharge while not flying
				if P_IsObjectOnGround(player.mo) == false then
					player.pflags = $1|PF_JUMPED --re-enable air and shield actions if they haven't been used (PF_THOKKED is *not* removed)
				end
			end
		end
	end
end)

addHook("MobjSpawn", function(mobj)
	if mobj and mobj.valid then
	
		if (mobj.flags & MF_NOGRAVITY) then
			mobj.naturalgravitydefiance = 1 --have a mobj record whether or not it naturally has MF_NOGRAVITY when it spawns so that the SpringShell ShouldDamage hook can temporarily remove it and the launch regulation MobjThinker can restore it
		else
			mobj.naturalgravitydefiance = 0 --...In hindsight, I could have just used (mobj.info.flags & MF_NOGRAVITY) to check whether or not an object has MF_NOGRAVITY as one of its built-in flags, but this code works fine, so I might as well use it.
		end
	end
end, MT_NULL)

addHook("MobjThinker", function(mobj)
	if mobj and mobj.valid then
	
		if mobj.sprungbycassandra == nil then
			mobj.sprungbycassandra = 0
		end
		
		if mobj.sprungbycassandra > 0 then
			if (leveltime%TICRATE) then
				mobj.sprungbycassandra = $ - 1
			end
		end
		
		if mobj.sprungbycassandra > TICRATE then
			if mobj.momz <= 13*FRACUNIT/2 then
				P_SetObjectMomZ(mobj, (abs(mobj.momz)+(13*FRACUNIT/2)), true) --prevent Cassandra's Spring Shell-induced ascent from being prematurely canceled
			end
		end
		
		if mobj.sprungbycassandra > 1
		and mobj.momz > 0 then
			mobj.flags = $&~MF_NOGRAVITY --prevent MF_NOGRAVITY from being reactivated while rising due to Cassandra's Spring Shell
		end
		
		if mobj.sprungbycassandra == 1 and mobj.naturalgravitydefiance == 1 then
			mobj.flags = $1|MF_NOGRAVITY --restore MF_NOGRAVITY to a mob that is naturally supposed to have it (such as a Buzz)
		end
	end
end, MT_NULL)

addHook("MobjThinker", function(cassandrasspikeball1)
	if cassandrasspikeball1 and cassandrasspikeball1.valid then
		if cassandrasspikeball1.speed == nil then
			cassandrasspikeball1.speed = cassandrasspikeball1.info.speed --set the speed value of the spikeball to the speed value given in MT_UNIBALL's parameters
		end
	end
end, MT_UNIBALL)

addHook("MobjThinker", function(cassandrasspikeball2)
	if cassandrasspikeball2 and cassandrasspikeball2.valid then
		if cassandrasspikeball2.speed == nil then
			cassandrasspikeball2.speed = cassandrasspikeball2.info.speed --set the speed value of the spikeball to the speed value given in MT_UNIBALL's parameters
		end
	end
end, MT_UNIBALL)

addHook("MobjThinker", function(cassandrasspikeball3)
	if cassandrasspikeball3 and cassandrasspikeball3.valid then
		if cassandrasspikeball3.speed == nil then
			cassandrasspikeball3.speed = cassandrasspikeball3.info.speed --set the speed value of the spikeball to the speed value given in MT_UNIBALL's parameters
		end
	end
end, MT_UNIBALL)

addHook("MobjThinker", function(cassandrasspikeball4)
	if cassandrasspikeball4 and cassandrasspikeball4.valid then
		if cassandrasspikeball4.speed == nil then
			cassandrasspikeball4.speed = cassandrasspikeball4.info.speed --set the speed value of the spikeball to the speed value given in MT_UNIBALL's parameters
		end
	end
end, MT_UNIBALL)

addHook("MobjThinker", function(cassandrasspikeball5)
	if cassandrasspikeball5 and cassandrasspikeball5.valid then
		if cassandrasspikeball5.speed == nil then
			cassandrasspikeball5.speed = cassandrasspikeball5.info.speed --set the speed value of the spikeball to the speed value given in MT_UNIBALL's parameters
		end
	end
end, MT_UNIBALL)

addHook("ShouldDamage", function(target, inflictor, source, damage, damagetype)
	if target.player.mo and target.player.mo.valid then
	
		if target.player.mo.skin ~= "cassandra_b" then
			return
		end
		
		if target.player.mo.copy == MT_SPRINGSHELL --Green Spring Shell spring armor
		and inflictor.z >= (target.player.mo.z + (target.player.height - (target.player.height / 3))) --if the attack came from above
		and target.player.mo.energy >= 15*FRACUNIT/2 then
			target.player.mo.energy = $ - 15*FRACUNIT/2
			P_SetObjectMomZ(inflictor, 50*FRACUNIT, false) --launch the would-be harmful object into the air
			inflictor.eflags = $1|MFE_SPRUNG
			inflictor.flags = $&~MF_NOGRAVITY --temporarily remove MF_NOGRAVITY so that P_SetObjectMomZ can't be cut short by things like Buzz or BASH chaser actions (and the inflictor can't just immediately descend to attack Cassandra again)
			inflictor.sprungbycassandra = 2*TICRATE --set a timer to prevent MF_NOGRAVITY from reactivating and automatically return it after a short time if the inflictor is supposed to naturally have it (so that it can resume its usual behavior when it's done being launched)
			return false
		end
		
		if target.player.mo.copy == MT_YELLOWSHELL --Yellow Spring Shell spring armor
		and inflictor.z >= (target.player.mo.z + (target.player.height - (target.player.height / 3))) --if the attack came from above
		and target.player.mo.energy >= 15*FRACUNIT/2 then
			target.player.mo.energy = $ - 15*FRACUNIT/2
			P_SetObjectMomZ(inflictor, 75*FRACUNIT, false) --launch the would-be harmful object into the air
			inflictor.eflags = $1|MFE_SPRUNG
			inflictor.flags = $&~MF_NOGRAVITY --temporarily remove MF_NOGRAVITY so that P_SetObjectMomZ can't be cut short by things like Buzz or BASH chaser actions (and the inflictor can't just immediately descend to attack Cassandra again)
			inflictor.sprungbycassandra = 5*TICRATE/2 --set a timer to prevent MF_NOGRAVITY from reactivating and automatically return it after a short time if the inflictor is supposed to naturally have it (so that it can resume its usual behavior when it's done being launched)
			return false
		end
		
		if target.player.mo.copy == MT_FACESTABBER
		and damagetype ~= DMG_DROWNED --as funny and awesome as it would be to avoid drowning by charging, that doesn't make sense
		and damagetype ~= DMG_SPACEDROWN
		and damagetype ~= DMG_DEATHPIT then --as funny and awesome as it would be to run across bottomless pits, that doesn't make sense either
			if target.player.speed >= 50*FRACUNIT then
				return false --shrug off damage while charging
			end
			if target.player.speed < 50*FRACUNIT --if the player isn't moving fast enough to simply shrug off damage
			and target.player.extrahit == 0
			and (damagetype == DMG_CRUSHED or (target.player.rings == 0 and target.player.powers[pw_shield] == 0))
			and P_PlayerInPain(target.player) == false --don't run this code twice in a row
			and target.player.mo.energy >= 50*FRACUNIT then
				target.player.mo.energy = $ - 50*FRACUNIT
				target.player.extrahit = 150*TICRATE --Lance-a-Bot only has 2 HP, you can't just tank endlessly
				print("You withstood damage because of your sturdy body!")
				P_DoPlayerPain(target.player, source, inflictor)
				target.player.powers[pw_flashing] = 3*TICRATE
				target.player.losstime = $ + 10*TICRATE
				target.player.timeshit = $ + 1
				return false
			end
		end
		
		if target.player.mo.copy == MT_EGGGUARD
		and damagetype ~= DMG_DROWNED --can't block lack of oxygen
		and damagetype ~= DMG_SPACEDROWN
		and damagetype ~= DMG_DEATHPIT then
			target.player.mo.copy = 0 --Egg Guard power absorbs the damage and is lost
			P_DoPlayerPain(target.player, source, inflictor) --apply knockback and mercy invincibility
			target.player.powers[pw_flashing] = 3*TICRATE
			target.player.losstime = $ + 10*TICRATE
			target.player.timeshit = $ + 1
			return false
		end
		
		if target.player.mo.copy == MT_GSNAPPER --Green Snapper spike armor
		and inflictor.z <= (target.player.mo.z - (target.player.height + (target.player.height / 3))) --if the attack came from below
		and target.player.mo.energy >= 10*FRACUNIT then
			target.player.mo.energy = $ - 10*FRACUNIT
			P_DamageMobj(inflictor, target.player.mo, target.player.mo, 1) --Spiked protrusion damage
			return false
		end
		
		if target.player.mo.copy == MT_MINUS
		and target.player.mo.minusdigging == 1
		and damagetype ~= DMG_DROWNED
		and damagetype ~= DMG_SPACEDROWN
		and damagetype ~= DMG_DEATHPIT then
			return false --can't be hurt while digging
		end
		
		if target.player.mo.copy == MT_PENGUINATOR
		and (target.player.mo.penguinslide2 >= 40 or target.player.mo.penguinslide3 >= 40)
		//and target.player.mo.energy >= 3*FRACUNIT
		and damagetype ~= DMG_DROWNED
		and damagetype ~= DMG_SPACEDROWN
		and damagetype ~= DMG_DEATHPIT then
			//target.player.mo.energy = $ - 3*FRACUNIT
			return false
		end
	end
end, MT_PLAYER)

addHook("MobjMoveBlocked", function(mobj, thing, line)
	if mobj.valid and mobj.player then
		local player = mobj.player
		if player.mo and player.mo.valid then
			
			if player.mo.skin ~= "cassandra_b" then
				return
			end
			
			if thing and thing.valid then
				if player.mo.copy == MT_SNAILER --if the player's current copy ability is the Snailer
				and player.custom2down > 1 --if the player is holding Custom2
				and player.mo.energy >= 2*FRACUNIT then --if the player has enough energy
					player.drawangle = (thing.angle+ANGLE_180)
					player.mo.snailerclimbing = $ + 1 --climb on the object blocking movement
					//P_InstaThrust(player.mo, player.mo.angle, -(player.speed)) --if player is making contact with a wall, stay in place without disabling cmd.forwardmove
				end
			end
			
			if line and line.valid then
				if player.mo.copy == MT_SNAILER --if the player's current copy ability is the Snailer
				and player.custom2down > 1 --if the player is holding Custom2
				and player.mo.energy >= 2*FRACUNIT then --if the player has enough energy
					player.drawangle = (R_PointToAngle2(line.v1.x, line.v1.y, line.v2.x, line.v2.y)+ANGLE_180)
					player.mo.snailerclimbing = $ + 1 --climb on the wall blocking movement
					//P_InstaThrust(player.mo, player.mo.angle, -(player.speed)) --if player is making contact with a wall, stay in place without disabling cmd.forwardmove
				end
			end
		end
	end
end, MT_PLAYER)

addHook("PlayerCmd", function(player, cmd)
	if player.mo and player.mo.valid then
	
		if player.mo.skin ~= "cassandra_b" then
			return
		end
		
		if player.mo.copy == MT_SNAILER
		and player.mo.snailerclimbing > 0 then
			cmd.sidemove = 0 --disable lateral movement while climbing with Snailer power
			/*if cmd.forwardmove ~= 0 then
				player.consecutivecrawl = $ + cmd.forwardmove --convert forwardmove into a new movement variable
			else
				player.consecutivecrawl = 0
			end
			if player.consecutivecrawl > 50 then
				player.consecutivecrawl = 50 --give consecutivecrawl the same caps as forwardmove
			end
			if player.consecutivecrawl < -50 then
				player.consecutivecrawl = -50
			end
			cmd.forwardmove = 0 --prevent actual forward or backward movement*/
		end
	end
end)

addHook("MobjMoveCollide", function(tmthing, thing)
	if tmthing.player.mo and tmthing.player.mo.valid then
	
		if tmthing.player.mo.skin ~= "cassandra_b" then
			return
		end
		
		if not (tmthing.player.mo.z >= (thing.z - tmthing.player.height) and tmthing.player.mo.z <= (thing.z + thing.height)) then --height check
			return false
		end
		
		if tmthing.player.mo.copy == MT_FACESTABBER then
			if (tmthing.player.speed >= 50*FRACUNIT and tmthing.player.speed < 80*FRACUNIT) then --if the player has built up speed
				if (thing.flags & MF_SHOOTABLE) then --if the thing being collided with is a normally-damageable object
					if thing.type == MT_PLAYER then
						if G_CoopGametype() == true --check whether or not the player being collided with is allied with Cassandra
						or (G_GametypeHasTeams == true and thing.player.ctfteam == tmthing.player.ctfteam) then
							return --don't harm friendly players 
						else
							P_DamageMobj(thing, tmthing, tmthing, 1)
						end
					else
						P_DamageMobj(thing, tmthing, tmthing, 1) --Damage enemies and monitors that Cassandra collides with while charging
					end
				end
				if thing.type == MT_SPIKE
				or thing.type == MT_WALLSPIKE
				or thing.type == MT_WALLSPIKEBASE
				or thing.type == MT_SPIKEBALL
				or thing.type == MT_UNIBALL
				or thing.type == MT_SMALLMACE
				or thing.type == MT_BIGMACE
				or thing.type == MT_EGGMOBILE4_MACE then
					P_KillMobj(thing, tmthing, tmthing) --smash spikes while charging
				end
			end
			if tmthing.player.speed >= 80*FRACUNIT then --if the player has built up a lot of speed
				if (thing.flags & MF_SHOOTABLE) then --if the thing being collided with is a normally-damageable object
					if thing.type == MT_PLAYER then
						if G_CoopGametype() == true --check whether or not the player being collided with is allied with Cassandra
						or (G_GametypeHasTeams == true and thing.player.ctfteam == tmthing.player.ctfteam) then
							return --don't harm friendly players 
						else
							P_DamageMobj(thing, tmthing, tmthing, 2)
						end
					else
						P_DamageMobj(thing, tmthing, tmthing, 2) --Damage enemies and monitors that Cassandra collides with while charging
					end
				end
				if thing.type == MT_SPIKE
				or thing.type == MT_WALLSPIKE
				or thing.type == MT_WALLSPIKEBASE
				or thing.type == MT_SPIKEBALL
				or thing.type == MT_UNIBALL
				or thing.type == MT_SMALLMACE
				or thing.type == MT_BIGMACE
				or thing.type == MT_EGGMOBILE4_MACE then
					P_KillMobj(thing, tmthing, tmthing) --smash spikes while charging
				end
			end
		end
		
		if tmthing.player.mo.copy == MT_PENGUINATOR
		and (tmthing.player.mo.penguinslide2 >= 25 or tmthing.player.mo.penguinslide3 >= 25) --ramming attack
		and (thing.flags & MF_SHOOTABLE) then --if the thing being collided with is a normally-damageable object
			if thing.type == MT_PLAYER then
				if G_CoopGametype() == true --check whether or not the player being collided with is allied with Cassandra
				or (G_GametypeHasTeams == true and thing.player.ctfteam == tmthing.player.ctfteam) then
					return --don't harm friendly players 
				else
					P_DamageMobj(thing, tmthing, tmthing, 1)
				end
			else
				P_DamageMobj(thing, tmthing, tmthing, 1) --Damage enemies and monitors that Cassandra collides with while sliding
			end
		end
	end
end, MT_PLAYER)

addHook("MobjMoveCollide", function(cassandrasshield, thing)
	if cassandrasshield and cassandrasshield.valid then
	
		if (thing.flags & MF_MISSILE) then --if a projectile hits the shield
			if thing.target ~= cassandrasshield.target then --and the projectile wasn't fired by Cassandra
				A_PlayAttackSound(cassandrasshield)
				return false --let projectiles fired by Cassandra through the shield
			else
				P_KillMobj(thing, cassandrasshield, cassandrasshield.target) --destroy projectiles that hit the shield
			end
		end
		
		if thing.type == MT_PLAYER then
			if thing == cassandrasshield.target then
				return false
			else
				A_PlayAttackSound(cassandrasshield)
				P_MoveOrigin(thing, (thing.x + cassandrasshield.target.rmomx), (thing.y + cassandrasshield.target.rmomy), (thing.z + (cassandrasshield.target.mo.momz + cassandrasshield.target.mo.pmomz + 5*FRACUNIT/2))) --I wanted to use rmomx and rmomy, but that returned errors
				P_InstaThrust(thing, cassandrasshield.target.angle, ((8*FRACUNIT)+cassandrasshield.target.speed)) --knock a player colliding with the shield away
			end
		end
		
		if (thing.flags & MF_ENEMY) then
			A_PlayAttackSound(cassandrasshield)
			P_MoveOrigin(thing, (thing.x + cassandrasshield.target.rmomx), (thing.y + cassandrasshield.target.rmomy), (thing.z + (cassandrasshield.target.mo.momz + cassandrasshield.target.mo.pmomz + 5*FRACUNIT/2)))
			P_InstaThrust(thing, cassandrasshield.target.angle, ((8*FRACUNIT)+cassandrasshield.target.speed)) --knock an enemy colliding with the shield away
		end
		
		if (thing.flags & MF_BOSS) then
			A_PlayAttackSound(cassandrasshield)
			P_MoveOrigin(thing, (thing.x + (cassandrasshield.target.rmomx/4)), (thing.y + (cassandrasshield.target.rmomy/4)), (thing.z + ((cassandrasshield.target.mo.momz + cassandrasshield.target.mo.pmomz + 5*FRACUNIT/2)/4)))
			P_InstaThrust(thing, cassandrasshield.target.angle, ((2*FRACUNIT)+cassandrasshield.target.speed)) --don't move a boss much, just in case moving it away from its axis and/or waypoints breaks its thinker
			if P_IsObjectOnGround(cassandrasshield.target) == true then
				P_MoveOrigin(cassandrasshield.target, (cassandrasshield.target.x + 0), (cassandrasshield.target.y + 0), (cassandrasshield.target.z + 7*FRACUNIT/2)) --put the player a short distance up in the air so that InstaThrust can work to full effect
				P_SetObjectMomZ(cassandrasshield.target, 3*FRACUNIT/2, true)
			else
				P_MoveOrigin(cassandrasshield.target, (cassandrasshield.target.x + 0), (cassandrasshield.target.y + 0), (cassandrasshield.target.z + 1*FRACUNIT))
				P_SetObjectMomZ(cassandrasshield.target, 4*FRACUNIT, true) --pop the player into the air so the following InstaThrust isn't cut off by the ground
			end
			P_InstaThrust(cassandrasshield.target, (cassandrasshield.target.angle+ANGLE_180), ((12*FRACUNIT)+cassandrasshield.target.speed)) --move the player away from the boss instead
		end
	end
end, MT_CASSANDRA_EGGSHIELD)

addHook("MobjDeath", function(target, inflictor, source, damagetype)
	if target.player and target.player.valid then
	
		if target.player.mo.skin ~= "cassandra_b" then
			return
		end
		
		if target.player.mo.copy == MT_YELLOWSHELL then
			P_SpawnMobjFromMobj(target.player.mo, 0, 0, 0, MT_YELLOWSPRING) --if Cassandra gets killed while copying a Yellow Spring Shell, she leaves a yellow spring behind, just like the enemy
			return
		end
	end
end, MT_PLAYER)