-- Functions
local function P_UpdatedTeleport(mo, x, y, z)
	if P_MoveOrigin then
		P_MoveOrigin(mo, x, y, z)
	else
		P_TeleportMove(mo, x, y, z)
	end

	return true
end

-----------------------
-- The Buzz itself l --
-----------------------

function A_ExplodeBuzz(actor, var1, var2)
	for k = 1, 5
		actor.thok = P_SpawnMobj(actor.x, actor.y, actor.z + 9*FRACUNIT, MT_SLOWFALLTHOK)
		actor.thok.color = SKINCOLOR_WHITE
		actor.thok.fuse = 3*TICRATE
		actor.thok.momx = P_RandomRange(-20, 20)*FRACUNIT
		actor.thok.momy = P_RandomRange(-20, 20)*FRACUNIT
		actor.thok.momz = P_RandomRange(1, 20)*FRACUNIT
	end
end

function A_ShockWaveBuzz(actor, var1, var2)
	local shockwave
	for a = 1, 36
		shockwave = P_SPMAngle(actor, var1, (actor.angle-360*ANG1)+((a*5)*ANG2))
		if shockwave and shockwave.valid then shockwave.scale = var2 end
	end
end

function A_BounceLandBuzz(actor, var1, var2)
	for i = 1, 3
		actor.missile = P_SPMAngle(actor, var1, actor.angle+(i*120*ANG1))
		if actor.missile and actor.missile.valid
			P_InstaThrust(actor.missile, actor.missile.angle, 30*FRACUNIT)
			P_SetObjectMomZ(actor.missile, 20*FRACUNIT, true)
		end
	end
end

function A_ExplodeBuzzAndShockwave(actor, var1, var2)
	A_ExplodeBuzz(actor, var1, var2)
	A_ShockWaveBuzz(actor, MT_SHOCKWAVE, FRACUNIT/2)
end

function A_BuzzFlee(actor, var1, var2)
	actor.flags = $|MF_NOGRAVITY
	actor.flags = $|MF_FLOAT
	P_SupermanLook4Players(actor)
end

function A_BuzzChooseAtk(mo)
	if mo.health > 4 then
		local atks = {S_BUZZ_CRASH1, S_BUZZ_SHOOT1, S_BUZZ_DASHATK1}
		mo.state = atks[P_RandomRange(1, #atks)]
	else
		local atks = {S_BUZZ_DASHATK1, S_BUZZ_PINCH1, S_BUZZ_SPINATK}
		mo.state = atks[P_RandomRange(1, #atks)]
	end
end

function A_BuzzDash(mo, var1, var2)
	if not mo.target or not mo.target.valid then
		P_LookForPlayers(mo, 5048*FRACUNIT, true)
		if not mo.target or not mo.target.valid then return end
	end

	mo.angle = R_PointToAngle2(mo.x, mo.y, mo.target.x, mo.target.y)

	local ghost = P_SpawnGhostMobj(mo)
	ghost.destscale = mo.scale*2

	if var1 then S_StartSound(mo, var1) end

	if var2 then
		local x = mo.target.x - 96*cos(mo.angle)
		local y = mo.target.y - 96*sin(mo.angle)
		local z = mo.target.z + 5*FRACUNIT
		P_UpdatedTeleport(mo, x, y, z)
	else
		local x = mo.target.x - P_RandomRange(300, 500)*cos(mo.angle)
		local y = mo.target.y - P_RandomRange(300, 500)*sin(mo.angle)
		local z = mo.target.z + P_RandomRange(50, 200)*FRACUNIT
		P_UpdatedTeleport(mo, x, y, z)
	end

	mo.momx = 0
	mo.momy = 0
	mo.momz = 0
	mo.angle = R_PointToAngle2(mo.x, mo.y, mo.target.x, mo.target.y)
end

freeslot("mt_bossbuzz")

addHook("MobjThinker", function(mo)
	mo.carrying = $ or {}
	mo.orbs = $ or {}
	mo.wait1 = $ or 0
	mo.wait2 = $ or 0
	mo.wait3 = $ or 0
	mo.scale = 2*FRACUNIT

	if mo.state == S_BUZZ_CRASH1
		P_SetObjectMomZ(mo, -20*FRACUNIT)
		
		if P_IsObjectOnGround(mo) then mo.state = S_BUZZ_CRASH2 end
	end

	if mo.state == S_BUZZ_SPINATK then
		mo.momx = 0
		mo.momy = 0
		mo.momz = 0
		mo.angle = $+ANGLE_22h

		local dust
		for i = 1, 12 do
			dust = P_SpawnMobj(mo.x, mo.y, mo.z+(mo.height/2), MT_SPINDUST)
			P_InstaThrust(dust, (i*30*ANG1)+mo.angle, 18*FRACUNIT)
		end

		if not (mo.tics%14) then
			local orb = P_SpawnMissile(mo, mo.target, MT_BUZZSHOT2)
			orb.momx = P_RandomRange(-24, 24)*FRACUNIT
			orb.momy = P_RandomRange(-24, 24)*FRACUNIT
			orb.momz = P_RandomRange(2, 16)*FRACUNIT
			S_StartSound(mo, sfx_buzz1)
		end
	end
	
	if mo.state == S_BUZZ_CRASH2
		mo.color = SKINCOLOR_WAVE
		mo.colorized = true
		mo.angle = R_PointToAngle2(mo.x, mo.y, mo.target.x, mo.target.y)
		
		mo.orbs = $ or {}
		
		local o = P_SpawnMobj(mo.x + P_RandomRange(-256, 256)*FRACUNIT,
		mo.y + P_RandomRange(-256, 256)*FRACUNIT,
		mo.z + P_RandomRange(-256, 256)*FRACUNIT, MT_THOK)
		o.color = SKINCOLOR_WHITE
		o.destscale = 1

		mo.orbs[#mo.orbs+1] = o
	end
	
	if mo.state == S_BUZZ_SHOOT1
		local an = mo.angle+P_RandomRange(-20, 20)*ANG1
		local ax = mo.x + 30*cos(an)
		local ay = mo.y + 30*sin(an)
		
		mo.orbs = $ or {}
		
		local o = P_SpawnMobj(ax, ay, mo.z + P_RandomRange(-30, 30)*FRACUNIT, MT_THOK)
		o.color = SKINCOLOR_WHITE
		o.destscale = 1
		mo.orbs[#mo.orbs+1] = o
		
		P_InstaThrust(mo, mo.angle, 0)
		P_SetObjectMomZ(mo, 0)
	elseif mo.state == S_BUZZ_SHOOT2
		P_InstaThrust(mo, mo.angle, 0)
		P_SetObjectMomZ(mo, 0)
	end
	
	for k,v in ipairs(mo.orbs)
		if v and v.valid
			local ghoss = P_SpawnGhostMobj(v)
			ghoss.destscale = 1

			local dist = P_AproxDistance(P_AproxDistance(mo.x - v.x, mo.y - v.y), mo.z - v.z)
			if dist ~= 0
				v.momx = (FixedMul(FixedDiv(mo.x - v.x, dist), 32<<FRACBITS))/2
				v.momy = (FixedMul(FixedDiv(mo.y - v.y, dist), 32<<FRACBITS))/2
				v.momz = (FixedMul(FixedDiv(mo.z - v.z, dist), 32<<FRACBITS))/2
			end
				
			local dist2 = R_PointToDist2(mo.x, mo.y, v.x, v.y)
			if dist2 < 10*FRACUNIT
				P_KillMobj(v, mo, mo)
				table.remove(mo.orbs, k)
			end
		end
	end
	
	if mo.state == S_BUZZ_CRASH3
		if mo.tics == 69 -- Nice
			S_StartSound(mo, sfx_trfire)
		end

		mo.colorized = false

		P_InstaThrust(mo, mo.angle, 50*FRACUNIT)
		P_SpawnMobj(mo.x, mo.y, mo.z, MT_METALJETFUME)
		P_SpawnGhostMobj(mo)
		
		local an = mo.angle
		local ax = mo.x + 10*cos(an)
		local ay = mo.y + 10*sin(an)
		local canmove = P_TryMove(mo, ax, ay, mo.z)

		if mo.carrying then
			local cx = mo.x + 50*cos(an)
			local cy = mo.y + 50*sin(an)
			for k,v in ipairs(mo.carrying)		
				if v and v.valid then
					P_UpdatedTeleport(v, cx, cy, mo.z)
					v.angle = R_PointToAngle2(v.x, v.y, mo.x, mo.y)		
					v.state = S_PLAY_PAIN
				end
			end
		end

		if not canmove then
			for k,v in ipairs(mo.carrying) do
				P_UpdatedTeleport(v, mo.x, mo.y, mo.z)
				P_DamageMobj(v, mo)
				table.remove(mo.carrying, k)
			end

			P_InstaThrust(mo, mo.angle, -15*FRACUNIT)
			P_StartQuake(20*FRACUNIT, TICRATE/2)

			mo.state = S_BUZZ_FLY
			P_LookForPlayers(mo, 3000*FRACUNIT, true)
		end
	else
		mo.carrying = {}
	end
	
	if mo.state == S_BUZZ_PINCH1 then
		mo.flags = $ & ~MF_NOGRAVITY
		mo.flags = $ & ~MF_FLOAT

		if P_IsObjectOnGround(mo) then
			P_LookForPlayers(mo, 3000*FRACUNIT, true)
			if mo.wait1 == 0 then
				S_StartSound(mo, sfx_pstop)
				P_StartQuake(10*FRACUNIT, TICRATE/8)

				local dust
				for i = 1, 36
					dust = P_SpawnMobj(mo.x, mo.y, mo.z, MT_SPINDUST)
					P_InstaThrust(dust, i*10*ANG1, 18*FRACUNIT)
				end

				mo.wait1 = 13
			end
		end
	elseif mo.state == S_BUZZ_PINCH2 then
		if P_IsObjectOnGround(mo) then
			P_LookForPlayers(mo, 3000*FRACUNIT, true)
			if mo.wait2 == 0 then
				S_StartSound(mo, sfx_pstop)
				P_StartQuake(10*FRACUNIT, TICRATE/8)

				local dust
				for i = 1, 36
					dust = P_SpawnMobj(mo.x, mo.y, mo.z, MT_SPINDUST)
					P_InstaThrust(dust, i*10*ANG1, 18*FRACUNIT)
				end

				mo.wait2 = 13
			end
		end
	elseif mo.state == S_BUZZ_PINCH3 then
		if P_IsObjectOnGround(mo) then
			P_LookForPlayers(mo, 3000*FRACUNIT, true)
			if mo.wait3 == 0 then
				S_StartSound(mo, sfx_pstop)
				P_StartQuake(10*FRACUNIT, TICRATE/8)

				local dust
				for i = 1, 36
					dust = P_SpawnMobj(mo.x, mo.y, mo.z, MT_SPINDUST)
					P_InstaThrust(dust, i*10*ANG1, 18*FRACUNIT)
				end

				mo.wait3 = 15
			end
		end
	end

	-- There is definitely a better way to code this
	if mo.wait1 > 0 then mo.wait1 = $-1 end
	if mo.wait2 > 0 then mo.wait2 = $-1 end
	if mo.wait3 > 0 then mo.wait3 = $-1 end
	if mo.wait1 == 1
		if mo.target then mo.angle = R_PointToAngle2(mo.x, mo.y, mo.target.x, mo.target.y) end
		P_InstaThrust(mo, mo.angle, 15*FRACUNIT)
		P_SetObjectMomZ(mo, 20*FRACUNIT)
		mo.state = S_BUZZ_PINCH2
	elseif mo.wait2 == 1
		if mo.target then mo.angle = R_PointToAngle2(mo.x, mo.y, mo.target.x, mo.target.y) end
		P_InstaThrust(mo, mo.angle, 15*FRACUNIT)
		P_SetObjectMomZ(mo, 20*FRACUNIT)
		mo.state = S_BUZZ_PINCH3
	elseif mo.wait3 == 1
		if mo.target then mo.angle = R_PointToAngle2(mo.x, mo.y, mo.target.x, mo.target.y) end
		P_InstaThrust(mo, mo.angle, 15*FRACUNIT)
		P_SetObjectMomZ(mo, 20*FRACUNIT)
		mo.flags = $|MF_NOGRAVITY|MF_FLOAT
		mo.state = S_BUZZ_PINCH4
	end
	
	if mo.state == S_BUZZ_PAIN2
		P_InstaThrust(mo, mo.angle, -50*FRACUNIT)
		P_SpawnMobj(mo.x, mo.y, mo.z, MT_METALJETFUME)

		local ghos = P_SpawnGhostMobj(mo)
		ghos.destscale = 1
		ghos.scalespeed = FRACUNIT/10
	end
	
	// Death
	if mo.state == S_BUZZ_D1
		if (leveltime%12==0)
			local explosion = P_SpawnMobj(mo.x + P_RandomRange(-50, 50)*FRACUNIT,
			mo.y + P_RandomRange(-50, 50)*FRACUNIT,
			mo.z + P_RandomRange(0, 50)*FRACUNIT,
			MT_BOSSEXPLODE)
			S_StartSound(explosion, sfx_cybdth)
		end
	elseif mo.state == S_BUZZ_D2
		if (leveltime%5==0)
			local explosion = P_SpawnMobj(mo.x + P_RandomRange(-50, 50)*FRACUNIT,
			mo.y + P_RandomRange(50, 50)*FRACUNIT,
			mo.z + P_RandomRange(0, 50)*FRACUNIT,
			MT_BOSSEXPLODE)
			S_StartSound(explosion, sfx_cybdth)
		end
	end
end, MT_BOSSBUZZ)

addHook("MobjDamage", function(mo, inf, src, dmg)
	mo.wait1 = 0
	mo.wait2 = 0
	mo.wait3 = 0
end)

addHook("ShouldDamage", function(t, i, s, d)
	if t.player and i and i.valid and i.state == S_BUZZ_CRASH3
		if t.state ~= S_PLAY_PAIN
			t.state = S_PLAY_PAIN
			table.insert(i.carrying, t)
		end

		return false
	end

	if t.type == MT_BOSSBUZZ
		if t.state == S_BUZZ_CRASH2 or t.state == S_BUZZ_SPINATK then
			S_StartSound(t, sfx_s3k7b)

			if (i.flags & MF_MISSILE)
				i.target = t
				i.angle = t.angle
				P_InstaThrust(i, i.angle, 15*FRACUNIT)
			else
				local a = R_PointToAngle2(t.x, t.y, i.x, i.y)
				P_InstaThrust(i, a, 10*FRACUNIT)
			end

			return false
		elseif t.state == S_BUZZ_CRASH3
			if i.state ~= S_PLAY_PAIN
				if i.player
					i.state = S_PLAY_PAIN
					table.insert(t.carrying, i)
				else
					P_DamageMobj(i, t)
				end
			end

			return false
		end
	end
end)

-------------------------
-- Now it's projectile --
-------------------------
freeslot("MT_BUZZSHOT1", "MT_BUZZSHOT2", "MT_SLOWFALLTHOK", "S_SLOWFALLTHOK")

mobjinfo[MT_SLOWFALLTHOK] = {
	doomednum = -1,
	spawnhealth = 1,
	spawnstate = S_SLOWFALLTHOK,
	radius = 15*FRACUNIT,
	height = 15*FRACUNIT,
	flags = MF_PAIN
}

states[S_SLOWFALLTHOK] = {SPR_THOK, A, -1, nil, 0, 0, S_NULL}

addHook("MobjThinker", function(mo)
	if mo and mo.valid
		mo.color = SKINCOLOR_WHITE

		if not (leveltime%3) then
			local ghos = P_SpawnGhostMobj(mo)
			ghos.destscale = 1
		end
	end
end, MT_BUZZSHOT1)

addHook("MobjThinker", function(mo)
	if mo and mo.valid
		P_SetObjectMomZ(mo, 3*(gravity/6), true)
		mo.color = SKINCOLOR_WHITE

		if not (leveltime%3) then
			local ghos = P_SpawnGhostMobj(mo)
			ghos.destscale = 1
		end

		if P_IsObjectOnGround(mo) then P_KillMobj(mo) end
	end
end, MT_BUZZSHOT2)

addHook("MobjThinker", function(mo)
	if mo and mo.valid
		P_SetObjectMomZ(mo, 3*(gravity/6), true)

		if not (leveltime%2) then
			local thok2 = P_SpawnMobj(mo.x, mo.y, mo.z, MT_THOK)
			thok2.scale = mo.scale
			thok2.destscale = 1
			thok2.color = mo.color
			thok2.sprite = mo.sprite
			thok2.frame = mo.frame
			thok2.tics = TICRATE
			thok2.fuse = TICRATE
		end
	end

	if P_IsObjectOnGround(mo) then P_RemoveMobj(mo) end
end, MT_SLOWFALLTHOK)

-----------
-- Done. --
-----------

-- This boss is for Cyberdime Realm heeehe
-- have fun.