-- Maimy Mace by j2b2
local MAIMY_SKIN_NAME = "maimy"
local MAIMY_MACE_OFFSET = 10*FRACUNIT -- How high from the bottom of the character the mace is
local MAIMY_MACE_SWING_OFFSET = 5*FRACUNIT -- How high from the bottom of the character the mace is when swinging
local MAIMY_MACE_COOLDOWN = TICRATE -- How long until Maimy can swing her mace again
local MAIMY_LAUNCH_SPEED = 15*FRACUNIT -- Speed at which you need to be going to valut
local MAIMY_BATTLE_SPECIAL_COOLDOWN = 4*TICRATE -- How long the battle special is on cool down for
local MAIMY_BATTLE_SPECIAL_RING_COST = 12 -- How many rings it costs to use the battle special
local MAIMY_BATTLE_SPECIAL_LENGTH = 2*TICRATE -- How long the battle special lasts for
local MAIMY_BATTLE_SPECIAL_SOUND_TIME = 10 -- How many ticks in between playing swing sound

local FRACUNIT_ONE_TENTH = FRACUNIT/10
local FRACUNIT2 = 2*FRACUNIT
local FRACUNIT6 = 6*FRACUNIT
local FRACUNIT8 = 8*FRACUNIT
local FRACUNIT10 = 10*FRACUNIT
local FRACUNIT60 = 60*FRACUNIT
local FRACUNIT240 = 240*FRACUNIT
local FRACUNIT360 = 360*FRACUNIT

freeslot(
	"MT_MAIMY_MACE",
	"MT_MAIMY_MACE_HURTBOX",
	"MT_MAIMY_SUPER_MACE",
	"MT_MAIMY_SUPER_MACE_HURTBOX"
)

-- Set the name for the BattleMod feed
mobjinfo[MT_MAIMY_MACE].name = "mace"
mobjinfo[MT_MAIMY_MACE_HURTBOX].name = "mace"
mobjinfo[MT_MAIMY_SUPER_MACE].name = "mace"
mobjinfo[MT_MAIMY_SUPER_MACE_HURTBOX].name = "mace"

local function IsValid(a)
	if a ~= nil then return a.valid end
	return false
end

local function CheckAndCrumble2(mo, sec)
	for fof in sec.ffloors()
		if not (fof.flags & FF_EXISTS) continue end -- Does it exist?
		if not (fof.flags & FF_BUSTUP) continue end -- Is it bustable?

		if mo.z + mo.momz + mo.height < fof.bottomheight continue end -- Are we too low?
		if mo.z + mo.momz > fof.topheight continue end -- Are we too high?

		EV_CrumbleChain(fof) -- Crumble
	end
end

local function MobjZIntersects(mobj1, mobj2)
	local mobj1Bottom, mobj1Top, mobj2Bottom, mobj2Top
	if P_MobjFlip(mobj1) == 1 then
		mobj1Bottom = mobj1.z
		mobj1Top = mobj1.z + mobj1.height
	else
		mobj1Bottom = mobj1.z - mobj1.height
		mobj1Top = mobj1.z
	end
	if P_MobjFlip(mobj2) == 1 then
		mobj2Bottom = mobj2.z
		mobj2Top = mobj2.z + mobj2.height
	else
		mobj2Bottom = mobj2.z - mobj2.height
		mobj2Top = mobj2.z
	end
	return mobj1Top >= mobj2Bottom and mobj1Bottom <= mobj2Top
end

local function MobjIntersects(mobj1, mobj2)
	return mobj1.x - mobj1.radius < mobj2.x + mobj2.radius and mobj1.x + mobj1.radius > mobj2.x - mobj2.radius
	and mobj1.y - mobj1.radius < mobj2.y + mobj2.radius and mobj1.y + mobj1.radius > mobj2.y - mobj2.radius
	and MobjZIntersects(mobj1, mobj2)
end

local function DamageMetal(ref, found)
	if found.type == MT_METALSONIC_BATTLE and found.flags2 == MF2_INVERTAIMABLE and MobjIntersects(ref, found) then
		found.flags2 = $ | MF2_CLASSICPUSH
	end
end

local function MaimyCanSwing(player)
	return 
	not IsValid(player.maimyMace) and 
	not P_PlayerInPain(player) and 
	player.powers[pw_carry] == CR_NONE and
	not P_SuperReady(player) and 
	player.exiting == 0 and 
	(P_IsObjectOnGround(player.mo) or (player.pflags & PF_JUMPED)) and
	(not (player.pflags & PF_THOKKED) or player.powers[pw_super])
end

-- reverbal: making another identical copy for spin, without the "PF_THOKKED" part
local function MaimyCanSpin(player)
	return 
	not IsValid(player.maimyMace) and 
	not P_PlayerInPain(player) and 
	--player.powers[pw_carry] == CR_NONE and
	not P_SuperReady(player) and 
	player.exiting == 0 
	--and (P_IsObjectOnGround(player.mo) or (player.pflags & PF_JUMPED)) 
	--and (not (player.pflags & PF_THOKKED) or player.powers[pw_super])
end

-- Create mace object.
local function MaimyDoMaceSwing(player)
	if IsValid(player.mo) and MaimyCanSwing(player) then
		if P_IsObjectOnGround(player.mo) then
			player.panim = PA_ABILITY2
			player.mo.state = S_PLAY_MAIMY_MELEE
		else
			player.pflags = player.pflags | PF_THOKKED
			player.mo.state = S_PLAY_ROLL
		end
		local maceMT, hurtboxMT
		if player.powers[pw_super] > 0 then
			maceMT = MT_MAIMY_SUPER_MACE
			hurtboxMT = MT_MAIMY_SUPER_MACE_HURTBOX
		else
			maceMT = MT_MAIMY_MACE
			hurtboxMT = MT_MAIMY_MACE_HURTBOX
		end
		player.maimyMace = P_SpawnMobj(player.mo.x,player.mo.y,player.mo.z,maceMT)
		player.maimyMace.target = player.mo
		player.maimyMace.movedir = 0
		local maimyMaceHurtbox = P_SpawnMobjFromMobj(player.mo,0,0,0,hurtboxMT)
		maimyMaceHurtbox.target = player.mo
		maimyMaceHurtbox.tracer = player.maimyMace
		maimyMaceHurtbox.scale = 0
		for i=1,states[S_MAIMY_RING].var1 do
			local ring = P_SpawnMobj(0,0,0,MT_MAIMY_RING)
			if player.powers[pw_super] > 0 then
				P_SetMobjStateNF(ring, S_MAIMY_SUPER_RING)
			end
			ring.target = player.maimyMace
			ring.tracer = player.mo
			ring.movecount = i
		end
		player.maimyMaceCooldown = MAIMY_MACE_COOLDOWN
		A_PlaySeeSound(player.maimyMace)
	end
end

local function MaimyDoMaceSpin(player)
	local playerMo = player.mo
	-- reverbal: replaced MaimyCanSwing with MaimyCanSpin
	if IsValid(playerMo) and MaimyCanSpin(player) then
		player.panim = PA_ABILITY2
		playerMo.state = S_PLAY_MAIMY_MELEE_SPIN
		player.charflags = $ & ~SF_DASHMODE
		player.normalspeed = (skins[playerMo.skin].normalspeed)/4
		local maceMT, hurtboxMT
		if player.powers[pw_super] > 0 then
			maceMT = MT_MAIMY_SUPER_MACE
			hurtboxMT = MT_MAIMY_SUPER_MACE_HURTBOX
		else
			maceMT = MT_MAIMY_MACE
			hurtboxMT = MT_MAIMY_MACE_HURTBOX
		end
		player.maimyMace = P_SpawnMobjFromMobj(playerMo,0,0,0,maceMT)
		player.maimyMace.target = playerMo
		player.maimyMace.movedir = 0
		if player.powers[pw_super] > 0 then
			P_SetMobjStateNF(player.maimyMace, S_MAIMY_SUPER_MACE_SPIN)
		else
			P_SetMobjStateNF(player.maimyMace, S_MAIMY_MACE_SPIN)
		end
		local maimyMaceHurtbox = P_SpawnMobjFromMobj(playerMo,0,0,0,hurtboxMT)
		maimyMaceHurtbox.target = playerMo
		maimyMaceHurtbox.tracer = player.maimyMace
		maimyMaceHurtbox.scale = 0
		for i=1,states[S_MAIMY_RING].var1 do
			local ring = P_SpawnMobj(0,0,0,MT_MAIMY_RING)
			if player.powers[pw_super] > 0 then
				P_SetMobjStateNF(ring, S_MAIMY_SUPER_RING)
			end
			ring.target = player.maimyMace
			ring.tracer = playerMo
			ring.movecount = i
		end
		if not P_IsObjectOnGround(playerMo) then
		P_SetObjectMomZ(playerMo,FixedMul(FRACUNIT10*P_MobjFlip(playerMo),playerMo.scale))
		end
	end
end

local function MaimyMaceSpinEnd(player)
	if IsValid(player.maimyMace) then
		player.maimyMace.state = S_NULL
		player.mo.state = S_PLAY_FALL
	end
	player.charflags = $ | SF_DASHMODE
	player.normalspeed = skins[player.mo.skin].normalspeed
end

function A_MaimyRing(mo, ringCount, var2)
	local target = mo.target -- Mace
	local tracer = mo.tracer -- Player
	local flip = P_MobjFlip(tracer)
	if not IsValid(target) or not IsValid(tracer) then
		mo.state = S_NULL
	else
		local nThirds = FixedDiv(FRACUNIT*mo.movecount,FRACUNIT*(ringCount+1))
		local x = target.x + FixedMul(tracer.x - target.x, nThirds)
		local y = target.y + FixedMul(tracer.y - target.y, nThirds)
		local z = target.z + FixedMul(tracer.z - target.z + tracer.height/3, nThirds)
		mo.scale = target.scale
		P_TeleportMove(mo,x,y,z)
	end
end

function A_MaimyMace(mo, distance, speed)
	if not IsValid(mo) or not IsValid(mo.target) or mo.target.skin ~= MAIMY_SKIN_NAME then
		mo.state = S_NULL
		return
	end
	local playerMo = mo.target
	local player = playerMo.player
	local playerScale = playerMo.scale
	local distance = FixedMul(distance,playerScale)
	local flip = P_MobjFlip(playerMo)

	for i = 1, 2 do -- Loop twice for more acurate collision checks
		local collide = false
		if mo.movedir < speed/6 then
			mo.movedir = mo.movedir + 1
			mo.movecount = 2
			mo.flags = $ | MF_NOCLIPTHING
		else
			mo.movedir = mo.movedir + 2
			mo.movecount = $ - 1
			mo.flags = $ & ~MF_NOCLIPTHING
			collide = true
		end
		local percent = FixedDiv(mo.movedir*FRACUNIT,speed*FRACUNIT)

		if not IsValid(playerMo) or P_PlayerInPain(player) or mo.target.player.playerstate & PST_DEAD or mo.target.player.powers[pw_carry] or percent > FRACUNIT then
			mo.state = S_NULL
			return
		else
			local aimAngle
			local angle

			-- Create mace ghosts.
			if mo.movecount <= 0 then
				A_GhostMe(mo)
				mo.movecount = 2
			end

			if player.maimyAimDirection then
				aimAngle = player.drawangle
			else
				player.drawangle = playerMo.angle
				aimAngle = playerMo.angle
			end

			-- Get mace angle and position.
			if flip == 1 then
				mo.flags2 = $ & ~MF2_OBJECTFLIP
				angle = FixedAngle(FixedMul(FRACUNIT360,percent)-FRACUNIT60)
			else
				mo.flags2 = $ | MF2_OBJECTFLIP
				angle = FixedAngle(FixedMul(FRACUNIT360,-percent)+FRACUNIT240)
			end
			local x = playerMo.x+FixedMul(FixedMul(sin(angle),cos(aimAngle)),distance)
			local y = playerMo.y+FixedMul(FixedMul(sin(angle),sin(aimAngle)),distance)
			local z = playerMo.z+FixedMul(cos(angle),distance)+FixedMul(MAIMY_MACE_OFFSET*flip,playerScale)
			mo.scale = FixedMul(min(FixedMul(percent,FRACUNIT6)+FRACUNIT_ONE_TENTH,FRACUNIT),playerScale)

			if not collide then
				P_TeleportMove(mo, x, y ,z)
			else
				-- Check for vertical collsion.
				if mo.floorz < z and mo.ceilingz >= z + mo.height or not collide then
					mo.z = z
				else
					local surfaceHit
					if mo.floorz >= z then
						mo.z = mo.floorz
						surfaceHit = 0
					else
						mo.z = mo.ceilingz - mo.height
						surfaceHit = 1
					end

					CheckAndCrumble2(mo,mo.subsector.sector)
					playerMo.state = S_PLAY_MAIMY_MELEE_FINISH
					if mo.state == S_MAIMY_SUPER_MACE then
						mo.state = S_MAIMY_SUPER_MACE_HIT
					else
						mo.state = S_MAIMY_MACE_HIT
					end

					-- Create dust cloud on impact.
					for ang = 0,360,30 do
						local dust = P_SpawnMobjFromMobj(mo,0,0,mo.height*surfaceHit,MT_DUST)
						P_InstaThrust(dust,ang*ANG1,FixedMul(FRACUNIT8,mo.scale))
						if surfaceHit == 0 then
							dust.momz = FixedMul(FRACUNIT2,mo.scale)
						else
							dust.momz = FixedMul(-FRACUNIT2,mo.scale)
						end
					end

					-- Vault if speed is high enough.
					if (player.cmd.buttons & BT_SPIN) and IsValid(player) and player.speed > FixedMul(MAIMY_LAUNCH_SPEED,playerScale) then
						player.pflags = player.pflags | PF_JUMPED | PF_THOKKED
						P_SetObjectMomZ(playerMo, FixedDiv(FixedHypot(playerMo.momx,playerMo.momy),FRACUNIT2), false)
						playerMo.state = S_PLAY_ROLL
					end
					return
				end

				-- Check for horizontal collisions
				if not P_TryMove(mo,x,y,true) and collide then
					playerMo.state = S_PLAY_MAIMY_MELEE_FINISH
					if mo.state == S_MAIMY_SUPER_MACE then
						mo.state = S_MAIMY_SUPER_MACE_HIT
					else
						mo.state = S_MAIMY_MACE_HIT
					end
					return
				end

			end
		end
	end
end

function A_MaimyMaceSpin(mo, distance, speed)
	if not IsValid(mo) or not IsValid(mo.target) or mo.target.skin ~= MAIMY_SKIN_NAME or P_PlayerInPain(mo.target.player) or mo.target.health <= 0 then
		if IsValid(mo.target) then
			MaimyMaceSpinEnd(mo.target.player)
		else
			mo.state = S_NULL
		end
		return
	end
	local playerMo = mo.target
	local player = mo.target.player
	local distance = FixedMul(distance,playerMo.scale)
	local flip = P_MobjFlip(playerMo)
	playerMo.state = S_PLAY_MAIMY_MELEE_SPIN

	if mo.movecount % 2 == 0 then
		A_GhostMe(mo)
	end
	if mo.movecount % MAIMY_BATTLE_SPECIAL_SOUND_TIME == 0 then
		A_PlaySeeSound(mo)
	end
	mo.angle = $ + FixedAngle(speed)
	mo.movecount = $ + 1
	mo.scale = playerMo.scale

	if flip == 1 then
		mo.flags2 = $ & ~MF2_OBJECTFLIP
	else
		mo.flags2 = $ | MF2_OBJECTFLIP
	end

	local x = FixedMul(cos(mo.angle),distance)
	local y = FixedMul(sin(mo.angle),distance)

	player.drawangle = mo.angle
	P_TeleportMove(mo,playerMo.x+x,playerMo.y+y,playerMo.z+FixedMul(MAIMY_MACE_SWING_OFFSET*flip,playerMo.scale))
end

function A_MaimyMaceHit(mo, quakeIntensity, quakeTime)
	if IsValid(mo.target) and mo.target.player == displayplayer then
		P_StartQuake(quakeIntensity,quakeTime,{x=mo.x,y=mo.y,z=mo.z},MAIMY_QUAKE_RADIUS)
	end
	A_PlayAttackSound(mo)
end

function A_MaimyMaceHurtboxChase(mo, hitScale)
	if IsValid(mo.target) and IsValid(mo.tracer) then
		local tracer = mo.tracer
		mo.flags = ($ & ~MF_NOCLIPTHING) | (tracer.flags & MF_NOCLIPTHING)
		if tracer.state == S_MAIMY_MACE_HIT or tracer.state == S_MAIMY_SUPER_MACE_HIT then
			mo.scale = FixedMul(tracer.scale, hitScale)
			if mo.fuse == 0 then mo.fuse = 2 end
		else
			mo.scale = mo.tracer.scale
		end
		A_CapeChase(mo,1 | (((-mo.info.height/2)/FRACUNIT) << 16),0)
		searchBlockmap("objects", DamageMetal, mo)
	elseif IsValid(mo) then
		mo.state = S_NULL
	end
end

COM_AddCommand("maimy_aimdirection",function(player,arg)
	if arg == nil then
		CONS_Printf(consoleplayer,'\n"maimy_aimdirection" is "'..(player.maimyAimDirection or 0)..'" default is "0"')
		CONS_Printf(consoleplayer,"\t0 - Swing towards camera")
		CONS_Printf(consoleplayer,"\t1 - Swing towards move direction")
	else
		local value = tonumber(arg)
		if value == 0 or value == 1 then
			player.maimyAimDirection = value
		else
			CONS_Printf(consoleplayer,"Invalid argument")
		end
	end
end)

-- Check for objects to damage.
local function MaimyMaceMoveCollide(mobj, toucher)
	if IsValid(mobj) and IsValid(mobj.tracer) and IsValid(toucher) and MobjZIntersects(mobj, toucher) then
		if toucher.type == MT_EGGSHIELD or toucher.type == MT_SPIKE or toucher.type == MT_WALLSPIKE then
			P_KillMobj(toucher,mobj,mobj.target,mobj.info.damage,mobj.mass)
		elseif (toucher.flags & (MF_ENEMY | MF_BOSS | MF_SHOOTABLE)) and not (toucher.flags & MF_MISSILE)
		and (toucher.type ~= MT_PLAYER or not (gametyperules & GTR_FRIENDLY) and gametype ~= GT_RACE and gametype ~= GT_COMPETITION) then
			P_DamageMobj(toucher,mobj,mobj.target,mobj.info.damage,mobj.mass)
		end
	end
	return false
end

-- Check for bustable walls to break.
local function MaimyMaceLineCollide(mo, line)
	for _, sec in ipairs({line.frontsector, line.backsector})
		CheckAndCrumble2(mo, sec)
	end
end

addHook("MobjMoveCollide", MaimyMaceMoveCollide, MT_MAIMY_MACE_HURTBOX)
addHook("MobjMoveCollide", MaimyMaceMoveCollide, MT_MAIMY_SUPER_MACE_HURTBOX)
addHook("MobjLineCollide", MaimyMaceLineCollide, MT_MAIMY_MACE)
addHook("MobjLineCollide", MaimyMaceLineCollide, MT_MAIMY_SUPER_MACE)

addHook("PlayerThink",function(player)
	if IsValid(player.mo) and player.mo.skin == MAIMY_SKIN_NAME then
		if player.maimyMaceCooldown then player.maimyMaceCooldown = $ - 1 end
		-- Unroll when falling.
		if player.mo.state == S_PLAY_ROLL and not P_IsObjectOnGround(player.mo) and not IsValid(player.maimyMace) and player.powers[pw_carry] == CR_NONE and player.mo.momz < 0 then
			player.mo.state = S_PLAY_FALL
		end
	else
		-- reverbal: miamy -> maimy: typo much?
		if IsValid(player.maimyMace) then
			player.maimyMace.state = S_NULL
		end
		player.maimyMace = nil
		player.maimyMaceCooldown = nil
	end
end)

addHook("PlayerCanDamage", function(player)
	if IsValid(player.mo) and player.mo.health and player.mo.skin == MAIMY_SKIN_NAME and not P_IsObjectOnGround(player.mo) and player.mo.state == S_PLAY_ROLL then
		return true
	end
end)

addHook("SpinSpecial",function(player)
	if IsValid(player.mo) and player.mo.skin == MAIMY_SKIN_NAME then
		if not (player.pflags & PF_SPINDOWN) and not player.maimyMaceCooldown then
			MaimyDoMaceSwing(player)
		end
		return true
	end
end)

local maimybattle = function(target, inflictor, source, damage, damagetype)
	--battlemod maimy check
	if not (CBW_Battle
	and inflictor
	and inflictor.valid
	and inflictor.type == MT_MAIMY_MACE_HURTBOX
	and inflictor.target)
		return
	end

	--parrying the mace triggers parry for its player
	CBW_Battle.GuardFunc.Parry(target, inflictor.target, source, damage, damagetype)
end
addHook("ShouldDamage",maimybattle,MT_PLAYER)

local BattleFuncSet = false
addHook("ThinkFrame",function()
	if (BattleFuncSet) or not(CBW_Battle and CBW_Battle.SkinVars) then return end
	BattleFuncSet = true

	local function MaimyBattleSpecial(mo, doaction)
		local player = mo.player
		if player.actionstate > 0 and (player.actiontime <= 0 or not IsValid(player.maimyMace)) then
			MaimyMaceSpinEnd(player)
			CBW_Battle.ApplyCooldown(player,MAIMY_BATTLE_SPECIAL_COOLDOWN)
			player.actionstate = 0
		elseif player.actionstate == 0 and IsValid(player.maimyMace) and player.maimyMace.state == S_MAIMY_MACE_SPIN then
			MaimyMaceSpinEnd(player)
			CBW_Battle.ApplyCooldown(player,MAIMY_BATTLE_SPECIAL_COOLDOWN)
		elseif IsValid(player.maimyMace) then
			player.canguard = false
			player.actiontime = max($ - 1, 0)
		elseif (CBW_Battle.CanDoAction(player)) then
			player.actionrings = MAIMY_BATTLE_SPECIAL_RING_COST
			player.actiontext = "Mace Spin"
			-- reverbal: replaced MaimyCanSwing with MaimyCanSpin
			if doaction == 1 and MaimyCanSpin(player) then
				CBW_Battle.PayRings(player)
				player.actiontime = MAIMY_BATTLE_SPECIAL_LENGTH
				player.actionstate = 1
				MaimyDoMaceSpin(player)
			end
		end
	end

	local function MaimyBattlePriority(player)
		if IsValid(player.maimyMace) and (player.maimyMace.state == S_MAIMY_MACE) then
			CBW_Battle.SetPriority(player,1,0,nil,1,0,"mace")
		end
	if IsValid(player.maimyMace) and (player.maimyMace.state == S_MAIMY_MACE_SPIN) then
			CBW_Battle.SetPriority(player,1,1,nil,1,1,"mace")
		end
	end

	CBW_Battle.SkinVars["maimy"] = {
		flags = SKINVARS_GUARD|SKINVARS_NOSPINSHIELD,
		weight = 90,
		shields = 1,
		special = MaimyBattleSpecial,
		guard_frame = 2,
		func_priority_ext = MaimyBattlePriority
	}
end)
-- Maimy Mace by j2b2
