// Chaser mode v0.1
//
// By JTE, and only JTE.
//
//

//
// "global" variables
// warning: because they are actually local, they won't naturally be networked :x
//

local playing
local playerstart
local boss

//
// function headers
//

local UnSeat
local GameStart

// set mobjinfo
mobjinfo[MT_BOSSRIDE] = {
	doomednum = 40,
	spawnhealth = 8,
	spawnstate = S_BOSSRIDE_FRONT,
	painstate = S_BOSSRIDE_PAIN,
	painsound = sfx_dmpain,
	deathstate = S_BOSSRIDE_DIE1,
	deathsound = sfx_cybdth,
	speed = 4,
	radius = 24*FRACUNIT,
	height = 76*FRACUNIT,
	activesound = sfx_telept,
	flags = MF_BOSS|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY|MF_SLIDEME
}

mobjinfo[MT_BOSSRIDE_BACK] = {
	spawnstate = S_BOSSRIDE_BACK,
	flags = MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT
}

mobjinfo[MT_PLAYERSTART] = {
	doomednum = 41,
	spawnstate = S_NULL,
	flags = MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOTHINK
}

// set states
states[S_BOSSRIDE_FRONT] = {SPR_RIDE,0}
states[S_BOSSRIDE_BACK] = {SPR_RIDE,1}
states[S_BOSSRIDE_PAIN] = {SPR_RIDE,0,40,A_Pain,0,0,S_BOSSRIDE_FRONT}

states[S_BOSSRIDE_DIE1] = {SPR_RIDE,0,8,A_Fall,0,0,S_BOSSRIDE_DIE2}
for i = S_BOSSRIDE_DIE2, S_BOSSRIDE_DIE13
	states[i] = {SPR_RIDE,0,8,A_BossScream,0,0,i+1}
end
states[S_BOSSRIDE_DIE14] = {SPR_RIDE,0,-1,function(actor) UnSeat(actor.rider.player) end}

//
// Map change, reset stuff
//
local startmus

addHook("MapChange", do
	playing = false
	playerstart = nil
	for player in players.iterate
		player.ready = false
		player.ride = nil
	end
	startmus = 0
end)

addHook("ThinkFrame", do
	if playerstart == nil
		for mt in mapthings.iterate
			if mt.type == 41 // Special creator.wad player start
				playerstart = {
					x = mt.x,
					y = mt.y,
					angle = mt.angle
				}
				return
			end
		end
		playerstart = false // none found, stop searching.
	end
end)

// Synchronise local variables over the network.
// No need to synchronize playerstart, it will be found locally next ThinkFrame.
addHook("NetVars", function(network) playing = network(playing) end)

//
// Level building mode
//

local function UnMarkSector(mobj)
	if not mobj.marksector
		return
	end
	if mobj.marksector.floorheight > 256*FRACUNIT
		mobj.marksector.floorpic = "GFZFLR02"
	else
		mobj.marksector.floorpic = "GFZFLR01"
	end
	mobj.marksector = nil
end

local function MarkSector(mobj)
	UnMarkSector(mobj)
	mobj.marksector = mobj.subsector.sector
	mobj.markpic = mobj.marksector.floorpic
	mobj.marksector.floorpic = "REDFLOOR"
end

local function Build(mobj, cmd)
	MarkSector(mobj)
	local sec = mobj.marksector
	if not (cmd.buttons & BT_JUMP)
		mobj.jumpdown = false
	elseif not mobj.jumpdown
		mobj.jumpdown = true
		if sec.floorheight < 384*FRACUNIT
			sec.floorheight = sec.floorheight + 64*FRACUNIT
		end
	end
	if not (cmd.buttons & BT_USE)
		mobj.spindown = false
	elseif not mobj.spindown
		mobj.spindown = true
		if sec.floorheight > 192*FRACUNIT
			sec.floorheight = sec.floorheight - 64*FRACUNIT
		end
	end
end

local function BossReady(mobj)
	UnMarkSector(mobj)
	P_TeleportMove(mobj, mobj.spawnpoint.x*FRACUNIT, mobj.spawnpoint.y*FRACUNIT, 384*FRACUNIT)
	mobj.angle = FixedAngle(mobj.spawnpoint.angle*FRACUNIT)
	mobj.rider.angle = mobj.angle
	mobj.tracer.angle = mobj.angle
	mobj.dummy.angle = mobj.angle
	mobj.flags = MF_BOSS|MF_SPECIAL|MF_SHOOTABLE|MF_NOGRAVITY|MF_SLIDEME
end

//
// fancey AI stuff from here down
//

local function GrabPlayer(mobj, player)
	// link us
	mobj.rider = player.mo
	player.ride = mobj

	// spawn back of the seat
	local back = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_BOSSRIDE_BACK)
	mobj.tracer = back

	// lock the player into his seat
	player.powers[pw_nocontrol] = 1<<15 // no moving
	player.mo.flags = MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT // no colliding with anything, and invisibility
	player.mo.angle = mobj.angle
	S_ChangeMusic("mapbld", true)
	S_SpeedMusic(2*FRACUNIT/3)
	CONS_Printf(player, "Setup the stage using Jump to raise the ground, and Spin to lower the ground.")
	CONS_Printf(player, "Type \"Drop\" to drop out and restart the level.")
	// spawn the dummy player
	local dummy = P_SpawnMobj(mobj.x, mobj.y, mobj.z, MT_PLAYER)
	mobj.dummy = dummy
	dummy.flags = MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT
	dummy.color = player.skincolor
	dummy.skin = player.mo.skin
	dummy.state = S_PLAY_TAP1
end

local function FindTarget(mobj)
	local eligable = {}
	for player in players.iterate
		if not player.ride and player.mo.skin
			table.insert(eligable, player)
		end
	end

	if #eligable > 0
		local player = eligable[P_RandomRange(1, #eligable)]
		GrabPlayer(mobj, player)
		print(player.name+" is now building the arena!\nType \"ready\" into the console to prepare for combat.")
	end
end

local function CheckWin()
	// Check if anyone is left alive...
	for player in players.iterate
		if player.playerstate == PST_LIVE and not player.ride and player.mo and player.mo.valid and player.mo.playing
			return false
		end
	end
	return true
end

local function BossThink(mobj)
	if not mobj.rider.valid
		mobj.rider = nil
	end
	if not mobj.rider
		return
	end

	if playing and CheckWin()
		S_StopMusic()
		// oh. we won. How rare!
		print(mobj.rider.player.name+" is victorious. >:)")
		playing = false
		S_StartSound(nil, sfx_lose)
		for player in players.iterate
			P_DoPlayerExit(player)
		end
		return
	end

	local player = mobj.rider.player

	// Steal the player's angle
	mobj.angle = mobj.rider.angle
	mobj.tracer.angle = mobj.rider.angle
	mobj.dummy.angle = mobj.rider.angle

	// Update these just in case
	mobj.dummy.color = player.skincolor
	mobj.dummy.skin = player.mo.skin

	local cmd = player.cmd

	local fwd = 0
	local side = 0
	local up = 0
	if cmd.forwardmove > 0
		fwd = 10*FRACUNIT
	elseif cmd.forwardmove < 0
		fwd = -10*FRACUNIT
	end
	if cmd.sidemove > 0
		side = 10*FRACUNIT
	elseif cmd.sidemove < 0
		side = -10*FRACUNIT
	end
	if playing
		if cmd.buttons & BT_JUMP
			up = 5*FRACUNIT
		end
		if cmd.buttons & BT_USE
			up = up-5*FRACUNIT
		end
	end

	// show hits left as rings
	player.rings = mobj.health + 1

	if not (mobj.flags2 & MF2_SKULLFLY)
		mobj.flags = mobj.flags | MF_SLIDEME
		P_InstaThrust(mobj, mobj.angle, fwd)
		local slope = mobj.rider.player.aiming
		if not playing
			slope = 0
		end
		mobj.momx = FixedMul(mobj.momx, cos(slope))
		mobj.momy = FixedMul(mobj.momy, cos(slope))
		mobj.momz = FixedMul(fwd, sin(slope))
		P_Thrust(mobj, mobj.angle-ANGLE_90, side)
		mobj.momz = mobj.momz+up
	end

	if playing and mobj.momz > 0 and mobj.z + mobj.momz > 448*FRACUNIT
		if mobj.flags2 & MF2_SKULLFLY
			mobj.momz = -mobj.momz
		else
			mobj.momz = 0
		end
	end

	if not playing
		Build(mobj, cmd)
	else
		// Fire missile!
		if not (cmd.buttons & BT_ATTACK)
			mobj.jumpdown = false
		elseif not mobj.jumpdown
			mobj.jumpdown = true
			local th = P_SpawnMobj(mobj.x, mobj.y, mobj.z + 2*mobj.info.height/3, MT_ROCKET)
			th.target = mobj
			if th.info.seesound
				S_StartSound(th, th.info.seesound)
			end

			local an = mobj.rider.angle
			local slope = mobj.rider.player.aiming
			local speed = th.info.speed
			th.angle = an
			th.momx = FixedMul(speed, cos(an))
			th.momy = FixedMul(speed, sin(an))

			th.momx = FixedMul(th.momx, cos(slope))
			th.momy = FixedMul(th.momy, cos(slope))

			th.momz = FixedMul(speed, sin(slope))
		end

		// Zoom attack!
		if not (cmd.buttons & BT_FIRENORMAL)
			mobj.spindown = false
		elseif not mobj.spindown and not (mobj.flags2 & MF2_SKULLFLY)
			mobj.spindown = true
			local th = mobj
			th.flags2 = th.flags2 | MF2_SKULLFLY
			th.flags = th.flags & ~MF_SLIDEME
			if th.info.activesound
				S_StartSound(th, th.info.activesound)
			end

			local an = mobj.rider.angle
			local slope = mobj.rider.player.aiming
			local speed = 60*FRACUNIT
			th.angle = an
			th.momx = FixedMul(speed, cos(an))
			th.momy = FixedMul(speed, sin(an))

			th.momx = FixedMul(th.momx, cos(slope))
			th.momy = FixedMul(th.momy, cos(slope))

			th.momz = FixedMul(speed, sin(slope))
		end
	end
end

local function BossOrder(mobj)
	// Reorder ourselves
	P_TeleportMove(mobj.tracer, mobj.x, mobj.y, mobj.z)
	P_TeleportMove(mobj.dummy, mobj.x, mobj.y, mobj.z + 36*FRACUNIT)
	P_TeleportMove(mobj, mobj.x, mobj.y, mobj.z)

	// Move the actual player to match
	P_TeleportMove(mobj.rider, mobj.x, mobj.y, mobj.z + 36*FRACUNIT)
	mobj.rider.momx = 0
	mobj.rider.momy = 0
	mobj.rider.momz = 0
end

addHook("BossThinker", function(mobj)
	if mobj.state == mobj.info.spawnstate
		mobj.flags2 = mobj.flags2 & ~MF2_FRET
	end
	if mobj.health == 0
		return
	end
	if not mobj.rider
		FindTarget(mobj)
		if not mobj.rider
			return
		end
	end
	BossThink(mobj)
	boss = mobj
end, MT_BOSSRIDE)

addHook("ThinkFrame", do
	// BossOrder has to happen in ThinkFrame, after all of the thinkers have run normally.
	if boss and boss.valid and boss.rider
		BossOrder(boss)
	else
		boss = nil
	end
end)

//
// Console command to pull the game out of building mode.
//

function GameStart()
	playing = true

	S_StopMusic()
	startmus = P_RandomRange(1,6)
	if startmus == 1
		S_ChangeMusic(mus_map03m, true)
	elseif startmus == 2
		S_ChangeMusic(mus_map12m, true)
	elseif startmus == 3
		S_ChangeMusic(mus_mapazm, true)
	elseif startmus == 4
		S_ChangeMusic(mus_map26m, true)
	elseif startmus == 5
		S_ChangeMusic(mus_map28m, true)
	elseif startmus == 6
		S_ChangeMusic(mus_mapb9m, true)
	end

	for mobj in mobjs.iterate(mobj)
		if mobj.type == MT_BOSSRIDE
			BossReady(mobj);
			if startmus == 3
				mobj.health = 9
			elseif startmus == 4
				mobj.health = 10
			elseif startmus == 5
				mobj.health = 12
			elseif startmus == 6
				mobj.health = 20
			end
			
		end
	end
	for player in players.iterate
		if player.ride
			CONS_Printf(player, "Shoot with RINGTOSS, Dash with RINGTOSSNORMAL")
		end
	end
	COM_BufInsertText(server, "csay Game start!")
	if playerstart
		for player in players.iterate
			if not player.ride and player.playerstate == PST_LIVE
				player.mo.playing = true
				player.rings = 2
				player.mo.health = 2
				P_TeleportMove(player.mo, playerstart.x*FRACUNIT, playerstart.y*FRACUNIT, 0)
				player.mo.angle = FixedAngle(playerstart.angle*FRACUNIT)
			end
		end
	end
end

local function levelfx()
	if not mapheaderinfo[gamemap].playerboss return end
	if startmus == 0
		P_SetupLevelSky(1)
		for sector in sectors.iterate do				
			if not sector.floorpic == "GFZFLR01"
				sector.floorpic = "GFZZFLR01"
			end
			if not sector.floorpic == "GFZFLR02"
				sector.floorpic = "GFZZFLR03"
			end
		end
	elseif startmus == 1
		for sector in sectors.iterate do
			if sector.floorpic == "REDFLOOR"
				if sector.floorheight > 256*FRACUNIT
					sector.floorpic = "GFZFLR02"
				else
					sector.floorpic = "GFZFLR01"
				end
			end
			if not sector.floorpic == "GFZFLR01"
				sector.floorpic = "GFZFLR01"
			end
			if not sector.floorpic == "GFZFLR02"
				sector.floorpic = "GFZFLR03"
			end
		end	
	elseif startmus == 2
		P_SetupLevelSky(29)
		for sector in sectors.iterate do
			if sector.floorpic == "REDFLOOR"
				if sector.floorheight > 256*FRACUNIT
					sector.floorpic = "GFZFLR02"
				else
					sector.floorpic = "GFZFLR01"
				end
			end					
			if not sector.floorpic == "GFZFLR01"
				sector.floorpic = "GFZFLR01"
			end
			if not sector.floorpic == "GFZFLR02"
				sector.floorpic = "GFZFLR03"
			end
		end
	elseif startmus == 3
		P_SetupLevelSky(4)
		for sector in sectors.iterate do
			if sector.floorpic == "REDFLOOR"
				if sector.floorheight > 256*FRACUNIT
					sector.floorpic = "CEZFLR03"
				else
					sector.floorpic = "CEZFLR01"
				end
			end					
			if sector.floorpic == "GFZFLR01"
				sector.floorpic = "CEZFLR01"
			end
			if sector.floorpic == "GFZFLR02"
				sector.floorpic = "CEZFLR03"
			end
		end
		for side in sides.iterate do
			if side.bottomtexture == 15
				side.bottomtexture = 211
			end
		end
	elseif startmus == 4
		P_SetupLevelSky(8)
		for sector in sectors.iterate do
			if sector.floorpic == "REDFLOOR"
				if sector.floorheight > 256*FRACUNIT
					sector.floorpic = "ERZRDCAU"
				else
					sector.floorpic = "ERZYLCAU"
				end
			end	
			if sector.floorpic == "GFZFLR01"
				sector.floorpic = "ERZYLCAU"
			end
			if sector.floorpic == "GFZFLR02"
				sector.floorpic = "ERZRDCAU"
			end
		end	
		for side in sides.iterate do
			if side.bottomtexture == 15
				side.bottomtexture = 429
			end
		end
	elseif startmus == 5
		P_SetupLevelSky(22)
		for sector in sectors.iterate do
			if sector.floorpic == "REDFLOOR"
				if sector.floorheight > 256*FRACUNIT
					sector.floorpic = "ERZRCKF2"
				else
					sector.floorpic = "ERZRCKF3"
				end
			end	
			if sector.floorpic == "GFZFLR01"
				sector.floorpic = "ERZRCKF3"
			end
			if sector.floorpic == "GFZFLR02"
				sector.floorpic = "ERZRCKF2"
			end			
		end
		for side in sides.iterate do
			if side.bottomtexture == 15
				side.bottomtexture = 469
			end
		end
	elseif startmus == 6
		P_SetupLevelSky(11)
		P_SwitchWeather(PRECIP_STORM)
		for sector in sectors.iterate do
			if sector.floorpic == "REDFLOOR"
				if sector.floorheight > 256*FRACUNIT
					sector.floorpic = "ERZGRYF1"
				else
					sector.floorpic = "ERZGRYF1"
				end
			end	
			if sector.floorpic == "GFZFLR01"
				sector.floorpic = "ERZGRYF1"
			end
			if sector.floorpic == "GFZFLR02"
				sector.floorpic = "ERZGRYF1"
			end
		end
		for side in sides.iterate do
			if side.bottomtexture == 15
				side.bottomtexture = 433
			end
		end
	end
end

addHook("ThinkFrame", levelfx)

local function ready(player)
	if not playerstart
		CONS_Printf(player, "Can't ready in this map.")
		return
	end
	if playing
		CONS_Printf(player, "Can't ready while already playing.")
		return
	end
	if player.ready
		CONS_Printf(player, "You're already ready!")
		return
	end
	print(player.name+" is ready! If you're not ready, please do \"ready\" in the console.")
	player.ready = true
	for player in players.iterate
		if player.playerstate == PST_LIVE and not player.ready
			return
		end
	end
	GameStart()
end

COM_AddCommand("ready", ready)
COM_AddCommand("ready2", ready, 2) // for splitscreen!

// Admin game start command. Skips the ready system.
COM_AddCommand("start", function(player)
	if playing
		return
	end
	GameStart()
end, COM_ADMIN)

function UnSeat(player)
	if not player.ride
		return
	end
	if not playing
		GameStart()
	end
	player.ride.tracer.state = S_NULL
	player.ride.dummy.state = S_NULL
	player.ride.state = S_NULL
	player.ride = nil
	player.mo.state = S_PLAY_ATK1
	player.mo.flags = MF_SOLID|MF_SHOOTABLE
	player.pflags = $1|PF_SPINNING
	player.rings = player.mo.health

	// Game over! New round!
	for player in players.iterate
		S_StopMusic()
		P_DoPlayerExit(player)
	end
end

// Eggman suicide command.
COM_AddCommand("drop", function(player)
	if not player.ride
		CONS_Printf(player, "You're not in the Egg Mobile.")
		return
	end
	print(player.name+" has dropped out! Resetting level...")
	UnSeat(player)
end)
