//Requires LUA_RBMB
freeslot(
	//"sfx_fanbnc",
	"MT_FANATIC",
	"MT_ROLLYBOMB",
	"MT_PLAYTRACKER",
	"MT_FANATIC_HEAD",
	"S_ATIC_EXIST",
	"S_ATIC_STILLEXIST",
	"S_ATIC_BOMB_ATTACK",
	"S_ATIC_BOMB_ATTACK_STALL",
	"S_ATIC_BOMB_ATTACKNOW",
	"S_ATIC_BOMB_ATTACK_AGAIN",
	"S_ATIC_BOMB_RETURN",
	"S_ATIC_BOMB_RETURN_STALL",
	"S_ATIC_DEATH_BEGIN",
	"S_ATIC_DEATH_MOVE1",
	"S_ATIC_DEATH_MOVE2",
	"S_ATIC_DEATH_SOUND",
	"S_ROLLYBOMB",
	"S_ATIC_DETECTHEAD",
	"S_ATIC_TARGET",
	"S_ATIC_TARGETHOLDING",
	"S_TRACKERSPAWN",
	"S_TRACKER1",
	"S_TRACKER2",
	"S_TRACKERFIRE",
	"S_TRACKERDIE",
	"S_TRACKERDIE2",
	"S_TRACKERDIEONLOOP",
	"S_TRACKERDIEALREADY",
	"S_TRACKERDIEALREADY2",
	"S_ATIC_PAIN1",
	"S_ATIC_PAIN2",
	"S_ATIC_PAIN3",
	"S_ATIC_PAIN4",
	"S_ATIC_PAINREPEAT",
	"SPR_FATB",
	"SPR_FATH",
	"SPR_RLBM",
	"SPR_TRKR",
	"S_HEAD_EXIST",
	"S_HEAD_EXIST2",
	"S_HEAD_EXIST3",
	"S_HEADACHE",
	"S_HEADACHE2",
	"S_HEADACHE3",
	"S_HEADACHE4",
	"S_HEADACHELOOP",
	"S_MIGRAIN",
	"S_HEADDEATH",
	"S_HEADDEATH2",
	"S_HEADDEATH3",
	"S_HEADDEATH4",
	"S_HEADDEATHSOUND",
	"S_HEADLOOP",
	"S_ATIC_HEADFALL",
	"S_HEAD_SHOOT1",
	"S_HEAD_SHOOT2",
	"S_HEAD_SHOOT3",
	"S_HEAD_SHOOT4",
	"S_HEAD_SHOOT5",
	"S_HEAD_SHOOT6",
	"S_HEADSHOOTLOOP",
	"S_ATIC_HEADHOLDING",
	"S_ATIC_TARGETCOOLOFF",
	"S_FAN_EXIST",
	"S_FAN_EXIST2",
	"S_FANDEATH",
	"S_FANDEATH2",
	"S_FANDEATH3",
	"S_FANDEATH4",
	"S_FANDEATH5",
	"MT_FANATIC_FAN",
	"SPR_ATFN")
//Fan-A/tic was made possible by Glaber, CobaltBW, MIDIMan, Katsy, Monster Iestyn, and Tatsuru on the #scripting channel of the SRB2 Discord.

// Function: A_HeadState
//
// Description: Chooses one of the two state numbers supplied Dependant on if Fan-A/tic's head exists.
//
// var1 = state number 1
// var2 = state number 2
//
function A_HeadState(actor, var1, var2)
 	if actor.head and actor.head.valid and actor.head.health > 0
 		actor.state = var1
 	else
 		actor.state = var2
 	end
end

// Determines how Fan-A/Tic will attack the player based on a random integer
function A_FanATicAttack(actor, var1, var2)
	local random = P_RandomByte()%3
	if (random) == 1
		actor.state = S_ATIC_BOMB_ATTACK
	else
		actor.state = S_ATIC_DETECTHEAD
	end
end

// Make the player bounce off of Fan-A/Tic's body
addHook("TouchSpecial", function(special, toucher)
	if not (special and special.valid
	and toucher and toucher.valid
	and toucher.player and toucher.player.valid)
		return
	end
	
	local playerSpeed = FixedHypot(toucher.momx, toucher.momy)
	local distXY
	local angle = R_PointToAngle2(special.x, special.y, toucher.x, toucher.y)
	
	local dx = abs(toucher.x - special.x)
	local dy = abs(toucher.y - special.y)
	
	if dx > dy
		distXY = dx
	else
		distXY = dy
	end
	
	if abs(playerSpeed) < 8*toucher.scale and distXY - toucher.radius < special.radius then playerSpeed = 8*toucher.scale end
	P_InstaThrust(toucher, angle, playerSpeed)
	toucher.momz = FixedMul(abs($), sin(R_PointToAngle2(0, special.z + (special.z + special.height)/2, distXY, toucher.z + (toucher.z + toucher.height)/2)))
	
	if (toucher.player.pflags & PF_GLIDING)
		toucher.player.pflags = $ & ~(PF_GLIDING|PF_JUMPED)
		toucher.state = S_PLAY_FALL
	end
	
	// Replace this sound with the TGF bounce sound, if desired
	S_StartSound(special, sfx_s3k7b)
	
	return true
end, MT_FANATIC)

--filters out with shootable flag, Contributed by Monster Iestyn
addHook("ShouldDamage", function(target, inflictor, source)
	if not (target and target.valid) then return end
	
	if target.fanaticHit then return false end
	
    if inflictor.valid and inflictor.type != MT_RETROBOBMMINE
        return false -- doesn't hurt
    end
    -- otherwise do as normal
end, MT_FANATIC)

--code chunk provided by Katsy and MIDIMan
addHook("MobjDamage", function(target, inflictor, source, damagetype)
	if not (target and target.valid) then return end
	
    if (inflictor.valid) and (inflictor.flags & MF_MISSILE)
        P_ExplodeMissile(inflictor)
    end
	
	// TouchSpecial doesn't 
	if target.health > 1
		target.fanaticHit = 24+TICRATE
	end
	
	target.health = $-1
	
	if target.health <= 0
		P_KillMobj(target, inflictor, source, damagetype)
		return true
	end
	
	target.state = target.info.painstate
	
	return true
end, MT_FANATIC)

mobjinfo[MT_FANATIC] = {
		//$Name "Fan-A/tic"
		//$Sprite FATHA0
		//$Category TGF/Concept Boss
        doomednum = 223,
        spawnstate = S_ATIC_EXIST,
        spawnhealth = 3,
        seestate = S_ATIC_STILLEXIST,
        seesound = sfx_None,
        reactiontime = 4,
        attacksound = sfx_None,
        painstate = S_ATIC_PAIN1,
        painchance = 0,
        painsound = sfx_dmpain,
        deathstate = S_ATIC_DEATH_BEGIN,
        xdeathstate = S_ATIC_DEATH_BEGIN,
        deathsound = sfx_cybdth,
        speed = 0,
        radius = 56*FRACUNIT,
        height = 154*FRACUNIT,
        dispoffset = 0,
        mass = 4,
        damage = 0,
        activesound = sfx_None,
		raisestate = S_NULL,
        flags = MF_SPECIAL|MF_BOSS|MF_NOGRAVITY|MF_SHOOTABLE|MF_SOLID
}
states[S_ATIC_EXIST] = {SPR_FATB, A, 1, A_Look, (576<<16)+1, 0, S_ATIC_EXIST} --spawn state. Lasts for 1 frame and triggers itself again if it doesn't see the player
states[S_ATIC_STILLEXIST] = {SPR_FATB, A, 1, A_FanATicAttack, 0, 0, S_ATIC_STILLEXIST} -- the see state. this state is set up to decide between the missel or meelee attacks

function A_SpawnRollyBomb(actor, var1, var2)
	if not (actor and actor.valid) then return end
	
	local rollyBomb = P_SpawnMobjFromMobj(actor, -70*cos(actor.angle), -70*sin(actor.angle), 64*FRACUNIT, MT_ROLLYBOMB)
	rollyBomb.angle = actor.angle
end

// Version of A_VileTarget that doesn't make the boss face the player
function A_FanaticTarget(actor, var1, var2)
	if not (actor and actor.valid) then return end
	
	local target = actor.target
	if not (target and target.valid) then return end
	
	local fogtype = MT_PLAYTRACKER
	
	local fog = P_SpawnMobjFromMobj(target, 0, 0, 0, fogtype)
	actor.tracer = fog
	fog.target = actor
	fog.tracer = target
	A_VileFire(fog)
end

states[S_ATIC_BOMB_ATTACK] = {SPR_FATB, A, 13, nil, 0, 0, S_ATIC_BOMB_ATTACK_STALL} --the first frame of the Missile state
states[S_ATIC_BOMB_ATTACK_STALL] = {SPR_FATB, N, 69, nil, 0, 0, S_ATIC_BOMB_ATTACKNOW} --Bomb door opened
states[S_ATIC_BOMB_ATTACKNOW] = {SPR_FATB, N, 1, A_SpawnRollyBomb, 0, 0, S_ATIC_BOMB_ATTACK_AGAIN} --Spawns a rolling mine.
states[S_ATIC_BOMB_ATTACK_AGAIN] = {SPR_FATB, N, 6, A_Repeat, 3, S_ATIC_BOMB_ATTACK_STALL, S_ATIC_BOMB_RETURN} --Go spawn a few more rolling mines
states[S_ATIC_BOMB_RETURN] = {SPR_FATB, N, 13, nil, 0, 0, S_ATIC_BOMB_RETURN_STALL} -- close the bomb door
states[S_ATIC_BOMB_RETURN_STALL] = {SPR_FATB, A, 104, nil, 0, 0, S_ATIC_EXIST} --Bomb door closed

states[S_ATIC_DETECTHEAD] = {SPR_FATB, A, 1, A_HeadState, S_ATIC_HEADFALL, S_ATIC_TARGET, S_ATIC_DETECTHEAD} --detects if the boss's head exists
states[S_ATIC_HEADFALL] = {SPR_FATB, A, 2, nil, 0, 0, S_ATIC_HEADHOLDING} --A trigger state for the head to move
states[S_ATIC_HEADHOLDING] = {SPR_FATB, A, 1, nil , sfx_cdfm70, 0, S_ATIC_HEADHOLDING} --meant to be a holding state.
states[S_ATIC_TARGET] = {SPR_FATB, A, 1, A_FanaticTarget, MT_PLAYTRACKER, 0, S_ATIC_TARGETHOLDING}
states[S_ATIC_TARGETHOLDING] = {SPR_FATB, A, 1, nil, 0, 0, S_ATIC_TARGETHOLDING}
states[S_ATIC_TARGETCOOLOFF] = {SPR_FATB, A, 70, nil, 1, 10, S_ATIC_EXIST} --State ment to add a delay before the next attack

states[S_ATIC_PAIN1] = {SPR_FATB, A, 1, A_Pain, 0, 0, S_ATIC_PAIN2}
states[S_ATIC_PAIN2] = {SPR_FATB, O, 1, nil, 0, 0, S_ATIC_PAIN3}
states[S_ATIC_PAIN3] = {SPR_FATB, P, 1, nil, 1, 1, S_ATIC_PAIN4}
states[S_ATIC_PAIN4] = {SPR_FATB, A, 1, nil, 1, 1, S_ATIC_PAINREPEAT}
states[S_ATIC_PAINREPEAT] = {SPR_FATB, A, 1, A_REPEAT, 15, S_ATIC_PAIN2, S_ATIC_EXIST}

states[S_ATIC_DEATH_BEGIN] = {SPR_FATB, A, 1, A_BossDeath, 0, 0, S_ATIC_DEATH_MOVE1}
states[S_ATIC_DEATH_MOVE1] = {SPR_FATB, A, 2, A_BossScream, 0, 0, S_ATIC_DEATH_MOVE2}
states[S_ATIC_DEATH_MOVE2] = {SPR_FATB, A, 0, A_Repeat, 35, S_ATIC_DEATH_MOVE1, S_ATIC_DEATH_SOUND}
states[S_ATIC_DEATH_SOUND] = {SPR_NULL, 0, 70, A_PlaySound, sfx_s1b9, 1, S_NULL}

addHook("MobjThinker", function(mo)
	if not (mo and mo.valid) then return end
	
	if mo.health <= 0 then return end
	
	if mo.state and mo.tics < states[mo.state].tics
		mo.frame = $-1
	end
	
	if not P_IsObjectOnGround(mo)
		P_SetObjectMomZ(mo, -8*FRACUNIT)
	else
		if mo.fuse <= 0 then mo.fuse = 84 end
		P_InstaThrust(mo, mo.angle, 8*mo.scale)
	end
end, MT_ROLLYBOMB)

addHook("MobjFuse", function(mo)
	if not (mo and mo.valid) then return end
	mo.momx = 0
	mo.momy = 0
	mo.momz = 0
	P_KillMobj(mo)
	return true
end, MT_ROLLYBOMB)

mobjinfo[MT_ROLLYBOMB] = {
        doomednum = -1,
        spawnstate = S_ROLLYBOMB,
        spawnhealth = 1000,
        //seestate = S_ROLLYMOVEA,
        seesound = sfx_None,
        reactiontime = 0,
        attacksound = sfx_None,
        painstate = S_NULL,
        painchance = -1,
        painsound = sfx_None,
        meleestate = S_NULL,
        missilestate = S_NULL,
        deathstate = S_XPLD1,
        xdeathstate = S_NULL,
        deathsound = sfx_s1c4,
        speed = 0,
        radius = 11*FRACUNIT,
        height = 22*FRACUNIT,
        dispoffset = 0,
        mass = 0,
        damage = 0,
        activesound = sfx_None,
		raisestate = S_NULL,
        flags = MF_SPECIAL|MF_PAIN
}

states[S_ROLLYBOMB] = {SPR_RLBM, G, 7, nil, 0, 0, S_ROLLYBOMB}

mobjinfo[MT_PLAYTRACKER] = {
        doomednum = -1,
        spawnstate = S_TRACKERSPAWN,
        spawnhealth = 1000,
        seestate = S_TRACKER1,
        seesound = sfx_None,
        reactiontime = 105,
        attacksound = sfx_None,
        painstate = S_NULL,
        painchance = -1,
        painsound = sfx_None,
        meleestate = S_TRACKERFIRE,
        missilestate = S_TRACKERFIRE,
        deathstate = S_NULL,
        xdeathstate = S_NULL,
        deathsound = sfx_None,
        speed = 10,
        radius = 11*FRACUNIT,
        height = 22*FRACUNIT,
        dispoffset = 0,
        mass = 4,
        damage = 0,
        activesound = sfx_None,
		raisestate = S_NULL,
        flags = MF_NOBLOCKMAP|MF_NOGRAVITY|MF_RUNSPAWNFUNC
}

function A_SpawnObjectRelativeScaled(actor, var1, var2)
	if not (actor and actor.valid) then return end
	
	local x, y, z
	local spawnType
	local mo
	
	x = var1/FRACUNIT
	y = var1 & 65535
	z = var2/FRACUNIT
	spawnType = var2 & 65535
	
	mo = P_SpawnMobjFromMobj(actor, x*FRACUNIT, y*FRACUNIT, z*FRACUNIT, spawnType)
	mo.angle = actor.angle
end

states[S_TRACKERSPAWN] = {SPR_TRKR, A, 1, A_look, (0<<16)+1, 0, S_TRACKERSPAWN}
states[S_TRACKER1] = {SPR_TRKR, A, 1, A_VileFire, 0, 0, S_TRACKER2}
states[S_TRACKER2] = {SPR_TRKR, B, 1, A_VileFire, 0, 0, S_TRACKERDIEONLOOP}
states[S_TRACKERDIEONLOOP] = {SPR_TRKR, A, 1, A_Repeat, 50, S_TRACKER1, S_TRACKERFIRE}
states[S_TRACKERFIRE] = {SPR_TRKR, B, 1, A_SpawnObjectRelativeScaled, 0, (700<<16)+MT_RETROBOBMMINE, S_TRACKERDIEALREADY}
states[S_TRACKERDIEALREADY] = {SPR_TRKR, B, 1, A_SetObjectTypeState, S_ATIC_TARGETCOOLOFF, (2816<<16)+MT_FANATIC, S_TRACKERDIEALREADY2} --set the body into cooldown
states[S_TRACKERDIEALREADY2] = {SPR_TRKR, A, 1, A_RemoteDamage, 0, 1, S_NULL}

mobjinfo[MT_FANATIC_HEAD] = {
        doomednum = -1,
        spawnstate = S_HEAD_EXIST,
        spawnhealth = 4,
        seestate = S_HEAD_EXIST,
        seesound = sfx_None,
        reactiontime = 8,
        attacksound = sfx_None,
        painstate = S_MIGRAIN,
        painchance = 0,
        painsound = sfx_dmpain,
        meleestate = S_NULL,
        missilestate = S_NULL,
        deathstate = S_HEADDEATH,
        xdeathstate = S_HEADDEATHSOUND,
        deathsound = sfx_cybdth,
        speed = 2*FRACUNIT,
        radius = 50*FRACUNIT,
        height = 100*FRACUNIT,
        dispoffset = 0,
        mass = 4,
        damage = 0,
        activesound = sfx_None,
		raisestate = S_NULL,
        flags = MF_SPECIAL|MF_BOSS|MF_NOGRAVITY|MF_SHOOTABLE|MF_SOLID|MF_NOCLIPHEIGHT
}

states[S_HEAD_EXIST] = {SPR_FATH, A, 1, nil, 0, 0, S_HEAD_EXIST2}
states[S_HEAD_EXIST2] = {SPR_FATH, B, 1, nil, 0, 0, S_HEAD_EXIST3}
states[S_HEAD_EXIST3] = {SPR_FATH, C, 1, nil, 0, 0, S_HEAD_EXIST}
states[S_MIGRAIN] = {SPR_FATH, D, 1, A_Pain, 0, 0, S_HEADACHE}
states[S_HEADACHE] = {SPR_FATH, A, 1, nil, S_ATIC_EXIST, (2816<<16)+MT_FANATIC, S_HEADACHE2}
states[S_HEADACHE2] = {SPR_FATH, D, 1, nil, 0, 0, S_HEADACHE3}
states[S_HEADACHE3] = {SPR_FATH, E, 1, nil, 0, 0, S_HEADACHE4}
states[S_HEADACHE4] = {SPR_FATH, A, 1, nil, 0, 0, S_HEADACHELOOP}
states[S_HEADACHELOOP] = {SPR_FATH, A, 1, A_Repeat, 15, S_HEADACHE2, S_HEAD_EXIST}

//states[S_HEADDEATH] = {SPR_FATH, A, 1, A_SetObjectTypeState, S_FANDEATH, (2816<<16)+MT_FANATIC_FAN, S_HEADDEATH2} --Kill the fan!
states[S_HEADDEATH] = {SPR_FATH, A, 1, A_BossScream, 0, 0, S_HEADDEATH2}
states[S_HEADDEATH2] = {SPR_FATH, B, 1, A_BossScream, 0, 0, S_HEADDEATH3}
states[S_HEADDEATH3] = {SPR_FATH, C, 1, A_BossScream, 0, 0, S_HEADLOOP}
states[S_HEADDEATHSOUND] = {SPR_NULL, 0, 70, A_PlaySound, sfx_crumbl, 1, S_NULL}
states[S_HEADLOOP] = {SPR_FATH, A, 1, A_Repeat, 25, S_HEADDEATH, S_NULL}
states[S_HEAD_SHOOT1] = {SPR_FATH, A, 1, nil, 0, 0, S_HEAD_SHOOT2}
states[S_HEAD_SHOOT2] = {SPR_FATH, B, 1, nil, 0, 0, S_HEAD_SHOOT3}
states[S_HEAD_SHOOT3] = {SPR_FATH, C, 1, nil, 0, 0, S_HEADSHOOTLOOP}
states[S_HEADSHOOTLOOP] = {SPR_FATH, A, 1, A_Repeat, 5, S_HEAD_SHOOT1, S_HEAD_SHOOT4}
states[S_HEAD_SHOOT4] = {SPR_FATH, A, 1, A_TrapShot, (0<<16)+MT_JETTBULLET, (0<<16)+0, S_HEAD_SHOOT5}
states[S_HEAD_SHOOT5] = {SPR_FATH, B, 1, nil, 0, 0, S_HEAD_SHOOT6}
states[S_HEAD_SHOOT6] = {SPR_FATH, C, 1, nil, 0, 0, S_HEAD_SHOOT1}

--Help from CobaltBW and MIDIMan
//Body spawn event
addHook("MapThingSpawn", function(mo, mthing)
	if not (mo and mo.valid) then return end
	
	if mthing and mthing.valid then mo.fanaticAngle = FixedAngle(mthing.angle*FRACUNIT) end
	
	if (mthing.options & MTF_OBJECTFLIP)
		mo.eflags = $|MFE_VERTICALFLIP
		mo.flags2 = $|MF2_OBJECTFLIP
	end
	
	if not mo.fanaticAngle then mo.fanaticAngle = mo.angle end

	//Spawn the head accordingly and store inside the body mobj as a variable
	mo.head = P_SpawnMobjFromMobj(mo, -8*cos(mo.fanaticAngle), -8*sin(mo.fanaticAngle), 132*FRACUNIT, MT_FANATIC_HEAD)
	mo.head.body = mo
	mo.head.angle = mo.fanaticAngle
	mo.head_speed = 2
	
	//this is meant to add the Fan (Moved from top of code for better organization)
	mo.fan = P_SpawnMobjFromMobj(mo, 75*cos(mo.fanaticAngle), 75*sin(mo.fanaticAngle), 50*FRACUNIT, MT_FANATIC_FAN)
    mo.fan.target = mo
	mo.fan.angle = mo.fanaticAngle
 
	//***
	mo.phase = 0 //We'll use this to keep track of which attack stage we're in
	mo.attacktics = 0 //Timer we'll use for determining when to fire projectiles
	mo.attackticslimit = 60 // Limits how far mo.attacktics can be incremented before resetting
	mo.bodyexplosionangle = 0 // Determines the angle at which an explosion should thrust from the middle of the body
	mo.bodyexplosiontimer = 0
	mo.bodyexplosiontimelimit = 3
end, MT_FANATIC)

//Body thinker event
addHook("MobjThinker", function(mo)
	if not (mo and mo.valid) then return end
	
	if mo.fanaticHit
		mo.fanaticHit = $-1
	end
	
	// A_VileTarget uses A_FaceTarget, which makes the rollybombs go the wrong direction, 
	// so force Fan-A/Tic to face its starting angle at all times
	if mo.angle ~= mo.fanaticAngle
		mo.angle = mo.fanaticAngle
	end
	
	if mo.state == S_ATIC_BOMB_ATTACK and mo.tics < states[mo.state].tics
		mo.frame = $+1
	elseif mo.state == S_ATIC_BOMB_RETURN and mo.tics < states[mo.state].tics
		mo.frame = $-1
	end
	
	local moFlip = P_MobjFlip(mo)
	
	local cosScaled = FixedMul(mo.scale, cos(mo.angle))
	local sinScaled = FixedMul(mo.scale, sin(mo.angle))
	
	// Moved from top of code for better organization
	// Edited to look "correct" from all angles
	if mo.fan and mo.fan.valid then
		P_TeleportMove(mo.fan, mo.x + (75*cosScaled), mo.y + (75*sinScaled),  mo.z + (50*mo.scale))
    end
	
	// Increment mo.attacktics by 1 to allow the head to fire bullets when necessary
	mo.attacktics = $+1

	if mo.head and mo.head.valid and mo.head.state ~= mo.head.info.xdeathstate
		// Store Fan-A/Tic's head as a variable for easier-to-read code
		local head = mo.head
		
		local zShift = 0
		if moFlip < 0 then zShift = mo.height - head.height end
		
		
		local headStartPos = {
			x = mo.x - 8*FixedMul(mo.scale, cos(mo.angle)), 
			y = mo.y - 8*FixedMul(mo.scale, sin(mo.angle)), 
			z = mo.z + zShift + P_MobjFlip(mo)*132*mo.scale
		}
		
		local headFallPos =	{
			x = mo.x + 185*FixedMul(mo.scale, cos(mo.angle)), 
			y = mo.y + 185*FixedMul(mo.scale, sin(mo.angle)), 
			z = mo.z + zShift - P_MobjFlip(mo)*78*mo.scale
		}
		
		// Don't let the head go above or below the floor, and therefore breaking the z-position check
		if headFallPos.z < head.floorz then headFallPos.z = head.floorz end
		if headFallPos.z > head.ceilingz - head.height then headFallPos.z = head.ceilingz - head.height end
		
		local headAngle = R_PointToAngle2(headStartPos.x, headStartPos.y, headFallPos.x, headFallPos.y)
		local headDistXY = R_PointToDist2(headStartPos.x, headStartPos.y, headFallPos.x, headFallPos.y)
		local headPitch = R_PointToAngle2(0, headStartPos.z, headDistXY, headFallPos.z)
		
		local headSpeeds = {x = 0, y = 0, z = 0}
		
		headSpeeds.x = FixedMul(FixedMul(head.scale, head.info.speed), FixedMul(cos(headAngle), cos(headPitch)))
		headSpeeds.y = FixedMul(FixedMul(head.scale, head.info.speed), FixedMul(sin(headAngle), cos(headPitch)))
		headSpeeds.z = FixedMul(FixedMul(head.scale, head.info.speed), sin(headPitch))
		
		// Let the head fire bullets when mo.attacktics reaches its limit
		if mo.attacktics >= mo.attackticslimit and head.health > 0
			// Only fire bullets if the head is below the center of the body
			if moFlip*(head.z - mo.z + 23*mo.scale) < 0
				local x, y, z = head.x, head.y, head.z
				if moFlip < 0 then z = $ + head.height end
				// Replace MT_JETTBULLET with a custom SRB2TGF-styled bullet, if necessary
				local bullet = P_SpawnPointMissile(mo.head, x + 128*cosScaled, y + 128*sinScaled, z,
					MT_JETTBULLET, x, y, z)
				if bullet and bullet.valid
					bullet.momx = $/2
					bullet.momy = $/2
					bullet.momz = $/2
				end
			end
			
			// Reset mo.attacktics
			mo.attacktics = 0
		end
		
		// How phases work:
		// 0 = Stays above body
		// 1 = Move towards ground
		// 2 = Stay on ground
		// 3 = Move towards top of body
		// 4 = Dead (Explosions)
		if mo.phase == 0
			// Prevent the head from moving too far to the right
			if moFlip*(head.z - headStartPos.z) > 0
				P_TeleportMove(head, headStartPos.x, headStartPos.y, headStartPos.z)
			end
		
			// Move to phase 1 if the head attack has been initiated
			if mo.state == S_ATIC_HEADFALL
				mo.phase = 1
			end
			
			// Move to phase 4 if the head is "dead"
			if head.health <= 0
				mo.phase = 4
			end
		elseif mo.phase == 1
			// Move to phase 2 if the head encounters its "falling" x position
			if moFlip*(head.z - headFallPos.z) < 0
				mo.phase = 2
			// Otherwise, move the head towards its "falling" x and z positions
			else
				P_TeleportMove(head,
							   head.x + headSpeeds.x,
							   head.y + headSpeeds.y,
							   head.z + headSpeeds.z)
			end
		elseif mo.phase == 2
			// Prevent the head from moving too far to the left
			if moFlip*(head.z - headFallPos.z) < 0
				P_TeleportMove(head, headFallPos.x, headFallPos.y, headFallPos.z)
			end
		elseif mo.phase == 3
			// Move to phase 0 if the head encounters its "starting" x position
			if moFlip*(head.z - headStartPos.z) > 0
				mo.state = S_ATIC_EXIST
				mo.phase = 0
			// Otherwise, move the head towards its "starting" x and z positions (2x faster than phase 1)
			else
				P_TeleportMove(head,
							   head.x - 2*headSpeeds.x,
							   head.y - 2*headSpeeds.y,
							   head.z - 2*headSpeeds.z)
			end
		elseif mo.phase == 4
			// Make a quick variable for the center of the 
			// Emulate the head explosions seen in SRB2TGF
			local explosionSpeed = 8
			
			// Replace MT_BOSSEXPLODE with a custom SRB2TGF-styled boss explosion, if desired
			local explosionObj = MT_BOSSEXPLODE
			
			// Fire 8 explosions in 8 different directions
			for i=0, 7 do
				local explosion = P_SpawnMobjFromMobj(mo, 23*cos(mo.angle), 23*sin(mo.angle), 91*FRACUNIT, explosionObj)
				explosion.momx = explosionSpeed*FixedMul(cosScaled, cos(i*ANGLE_45))
				explosion.momy = explosionSpeed*FixedMul(sinScaled, cos(i*ANGLE_45))
				P_SetObjectMomZ(explosion, explosionSpeed*sin(i*ANGLE_45))
			end
			
			A_BossDeath(head)
			if mo.fan and mo.fan.valid
				A_BossDeath(mo.fan)
			end
		end
	end
	
	if mo.health <= 0 and mo.state ~= S_ATIC_DEATH_SOUND
		mo.bodyexplosiontimer = $-1
		
		// Increment the angle at which the body explosion is thrusted to at least make a complete 
		// 360-degree rotation in 2 seconds
		mo.bodyexplosionangle = $-(936*FRACUNIT)
		
		// Emulate the head explosions seen in SRB2TGF
		local bodyExplosionSpeed = 8
			
		// Replace MT_BOSSEXPLODE with a custom SRB2TGF-styled boss explosion
		local bodyExplosionObj = MT_BOSSEXPLODE
		
		// Spawn an explosion being thrust at the angle set by mo.bodyexplosionangle when the timer hits 0
		if (mo.bodyexplosiontimer <= 0)
			local bodyExplosion = P_SpawnMobjFromMobj(mo, 0, 0, 91*FRACUNIT, bodyExplosionObj)
			P_SetObjectMomZ(bodyExplosion, bodyExplosionSpeed*sin(mo.bodyexplosionangle))
			bodyExplosion.momx = bodyExplosionSpeed*FixedMul(cosScaled, cos(mo.bodyexplosionangle))
			bodyExplosion.momy = bodyExplosionSpeed*FixedMul(sinScaled, cos(mo.bodyexplosionangle))
			
			// Reset the timer
			mo.bodyexplosiontimer = mo.bodyexplosiontimelimit
		end
	end
end, MT_FANATIC)

addHook("MobjDeath", function(head)
	if not (head and head.valid) return end
	
	// Just in case the MobjDamage hook never gets called
	head.body.phase = 3
	
	if head.body and head.body.valid
		local body = head.body
		if body.fan and body.fan.valid
			body.fan.state = body.fan.info.meleestate
		end
	end
end, MT_FANATIC_HEAD)

addHook("MobjDamage", function(head, inflictor)
	if not (head and head.valid
	and inflictor and inflictor.valid)
		return
	end
	
	if inflictor and inflictor.valid
		if head.body and head.body.valid
			head.body.phase = 3
		end
	end
end, MT_FANATIC_HEAD)

// Randomize Fan-A/Tic's explosion angle, make it intangible, and let it fall slowly when it dies
addHook("BossDeath", function(mo)
	if not (mo and mo.valid) then return end
	
	mo.bodyexplosionangle = P_RandomFixed()*FRACUNIT
	mo.flags = $|MF_NOCLIP|MF_NOCLIPHEIGHT & ~MF_SPECIAL
	P_SetObjectMomZ(mo, -FRACUNIT)
	
	// If the body for whatever reason dies before the head, 
	// kill Fan-A/Tic's remaining parts so they don't linger around
	if mo.fan and mo.fan.valid then
		local sector
		if mo.fan.subsector and mo.fan.subsector.valid then sector = mo.fan.subsector.sector end
		P_LinedefExecute(8, mo.fan, sector)
		A_BossDeath(mo.fan)
	end
	if mo.head and mo.head.valid then A_BossDeath(mo.head) end
	
	return true
end, MT_FANATIC)

//the FanATic's fan

mobjinfo[MT_FANATIC_FAN] = {
        doomednum = -1,
        spawnstate = S_FAN_EXIST,
        spawnhealth = 1000,
        seestate = S_FAN_EXIST,
        seesound = sfx_None,
        reactiontime = 8,
        attacksound = sfx_None,
        painstate = S_NULL,
        painchance = 0,
        painsound = sfx_dmpain,
        meleestate = S_FANDEATH,
        missilestate = S_NULL,
        deathstate = S_FANDEATH3,
        xdeathstate = S_NULL,
        deathsound = sfx_cybdth,
        speed = 0,
        radius = 36*FRACUNIT,
        height = 62*FRACUNIT,
        dispoffset = 0,
        mass = 4,
        damage = 0,
        activesound = sfx_None,
		raisestate = S_NULL,
        flags = MF_NOBLOCKMAP|MF_NOGRAVITY
}

states[S_FAN_EXIST] = {SPR_ATFN, A, 1, nil, 0, 0, S_FAN_EXIST2}
states[S_FAN_EXIST2] = {SPR_ATFN, B, 1, nil, 0, 0, S_FAN_EXIST}
states[S_FANDEATH] = {SPR_ATFN, A, 1, A_LinedefExecute, 8, 0, S_FANDEATH2}
states[S_FANDEATH2] = {SPR_ATFN, A, 1, A_RemoteDamage, 0, 1, S_FANDEATH2}
states[S_FANDEATH3] = {SPR_ATFN, A, 1, A_BossScream, 0, 0, S_FANDEATH4}
states[S_FANDEATH4] = {SPR_ATFN, A, 1, A_BossScream, 0, 0, S_FANDEATH3}
states[S_FANDEATH5] = {SPR_ATFN, A, 1, A_Repeat, 10, S_FANDEATH3, S_NULL}

