// Character swapper by Tatsuru
//
// This code is absolutely disgusting I'm okay with reusage but
// PLEASE ask me first so I don't die of second hand embarassment
// if you really want to use it

freeslot("MT_CHARASWAPPER", "S_CHARASWAPPER", "SPR_CHAR", "SPR_CHTB", "SPR_HTRG")

mobjinfo[MT_CHARASWAPPER] = {
	--$Name Character Swapper
	--$Sprite CHARG0
	--$Category OLDC
	doomednum = 3503,
	spawnstate = S_INVISIBLE,
	radius = 8*FRACUNIT,
	height = 8*FRACUNIT,
	flags = MF_NOGRAVITY|MF_SPECIAL
}

states[S_CHARASWAPPER] = {
	sprite = SPR_CHAR,
	frame = A|FF_PAPERSPRITE|FF_ANIMATE,
	var1 = 8,
	var2 = 2,
}

local function P_TeleportRelative(o, x, y, z, mo)
	P_MoveOrigin(
		o,
		mo.x + FixedMul(x, mo.scale),
		mo.y + FixedMul(y, mo.scale),
		o.origz or o.z
	)
end

addHook("MapThingSpawn", function(mobj, mt)
    local mo = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CHARASWAPPER)

	if multiplayer
		P_RemoveMobj(mo)
		return
	end
	
	local sec = R_PointInSubsector(mo.x, mo.y).sector
	local floorz = sec.f_slope and P_GetZAt(sec.f_slope, mo.x, mo.y) or sec.floorheight
	
	mo.z = floorz + 96*FRACUNIT
	mo.scale = FRACUNIT*2
	
	mo.panels = {}
	
	for i = 0, 3
		local ang = mo.angle + 90*i*ANG1
		local panel = P_SpawnMobjFromMobj(mo, 26*cos(ang), 26*sin(ang), 0, MT_THOK)
		panel.angle = ang + ANGLE_90
		panel.state = S_CHARASWAPPER
		panel.flags = $ | (MF_SOLID|MF_PAPERCOLLISION) & ~(MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT)
		panel.id = i
		panel.height = 56*FRACUNIT
		panel.origz = panel.z
		
		table.insert(mo.panels, panel)
	end
	
	mo.tube = {}
	
	for i = 0, 3
		for height = 0, 28*FRACUNIT, 28*FRACUNIT
			local ang = mo.angle + 90*i*ANG1
			local tube = P_SpawnMobjFromMobj(mo, 23*cos(ang), 23*sin(ang), height + 2*FRACUNIT, MT_THOK)
			tube.angle = ang
			tube.tics = -1
			tube.sprite = SPR_CHTB
			tube.frame = A
			tube.id = i
			tube.renderflags = $ | (RF_FLOORSPRITE|RF_SLOPESPLAT|RF_NOSPLATBILLBOARD|RF_NOSPLATROLLANGLE)
			P_CreateFloorSpriteSlope(tube)
			
			tube.floorspriteslope.o = {x = tube.x, y = tube.y, z = tube.z}
			tube.floorspriteslope.xydirection = tube.angle
			tube.floorspriteslope.zangle = height and 140*ANG1 or 30*ANG1
			
			table.insert(mo.tube, tube)
		end
	end
	
	mo.hole = {}
	
	for i = 0, 3
		for height = 0, 32*FRACUNIT, 32*FRACUNIT
			local ang = mo.angle + 90*i*ANG1
			local hole = P_SpawnMobjFromMobj(mo, 16*cos(ang), 16*sin(ang), height or -16*FRACUNIT, MT_THOK)
			hole.angle = ang + ANGLE_90
			hole.tics = -1
			hole.sprite = SPR_CHTB
			hole.frame = B|FF_PAPERSPRITE
			hole.id = i
			
			table.insert(mo.hole, hole)
		end
	end
	
	local target = P_SpawnMobj(mo.x, mo.y, floorz, MT_THOK)
	target.tics = -1
	target.sprite = SPR_HTRG
	target.frame = A
	target.renderflags = $ | (RF_FLOORSPRITE|RF_OBJECTSLOPESPLAT|RF_NOSPLATBILLBOARD)
end, MT_CHARASWAPPER)

local mobjtohandle

addHook("MobjThinker", function(mo)
	mo.angle = $ + ANG2
	
	if mo.panels == nil then return end
	
	for _, o in ipairs(mo.panels)
		local ang = mo.angle + 90*o.id*ANG1
		
		o.flags = $ & ~MF_SOLID
		
		P_TeleportRelative(o, 26*cos(ang), 26*sin(ang), 0, mo)
		o.angle = ang + ANGLE_90
		o.z = o.origz
		
		o.flags = $ | MF_SOLID
	end
	
	for _, o in ipairs(mo.tube)
		local ang = mo.angle + 90*o.id*ANG1
		
		P_TeleportRelative(o, 23*cos(ang), 23*sin(ang), 0, mo)
		o.angle = ang
		o.floorspriteslope.o = {x = o.x, y = o.y, z = o.z}
		o.floorspriteslope.xydirection = o.angle
	end
	
	for _, o in ipairs(mo.hole)
		local ang = mo.angle + 90*o.id*ANG1
		
		P_TeleportRelative(o, 16*cos(ang), 16*sin(ang), 0, mo)
		o.angle = ang + ANGLE_90
	end
end, MT_CHARASWAPPER)

addHook("MobjCollide", function(mo, tmo)
	if mo.id == nil return end
	if tmo.type == MT_THOK and tmo.id != nil return false end
	if tmo.type == MT_CHARASWAPPER return false end
end, MT_THOK)

addHook("TouchSpecial", function(mo, toucher)
	if toucher.player.bot return true end
	if toucher.player.charaswap return true end
	if toucher.player.charaswap_timer return true end
	
	toucher.momx = 0
	toucher.momy = 0
	toucher.momz = 0
	
	toucher.flags = $ | MF_NOGRAVITY
	toucher.player.charaswap = mo
	
	S_StartSound(toucher, sfx_s3kc3s)
	
	return true
end, MT_CHARASWAPPER)

addHook("PreThinkFrame", do
	for p in players.iterate
		p.prevdir = p.dir
		
		p.dir = {
			forwardmove = p.cmd.forwardmove,
			sidemove = p.cmd.sidemove,
		}
	end
end)

addHook("PostThinkFrame", do
	for p in players.iterate
		p.buttonhistory = p.cmd.buttons
	end
end)

local function GetRelativeControlDirection(p, deadzone)
	local tempangle, origtempangle = p.mo.angle, p.mo.angle
	
	if not p.dir.forwardmove and not p.dir.sidemove return end
	
	if FixedHypot(p.dir.forwardmove, p.dir.sidemove) < deadzone return end
	
	local tempx = FixedMul(p.dir.forwardmove*FRACUNIT,cos(tempangle))
	local tempy = FixedMul(p.dir.forwardmove*FRACUNIT,sin(tempangle))

	tempangle = origtempangle - ANGLE_90

	tempx = $ + FixedMul(p.dir.sidemove*FRACUNIT, cos(tempangle))
	tempy = $ + FixedMul(p.dir.sidemove*FRACUNIT, sin(tempangle))
	
	local controllerdirection = R_PointToAngle2(0, 0, tempx, tempy)
	local dangle = controllerdirection - p.mo.angle

	if (dangle > ANGLE_180) //flip to keep to one side
		dangle = InvAngle(dangle)
	end
	
	return dangle
end

local leftskin = 0
local rightskin = 0

local limit = 7
local ylimit = TICRATE/3
local xscroll = limit
local yscroll = 0
local leftarrowscale = 0
local rightarrowscale = 0
local decrease = function(x) return x - 1 < 0, x - 1 end
local increase = function(x) return x + 1 > limit, x + 1 end

local func

addHook("PlayerSpawn", function(p)
	p.charaswap = nil
	p.charaswap_timer = nil
	p.charaswap_accepted = nil
	
	xscroll = limit
	yscroll = 0
end)

addHook("PlayerThink", function(player)
	if player.bot return end
	
	if not player.charaswap or player.charaswap_timer
		player.newskin = player.mo and player.mo.skin or player.skin
		
		if player.charaswap_accepted
			yscroll = $ and min($ + 1, ylimit*2) or 0
		else
			yscroll = $ and max(0, $ - 1) or 0
		end
		
		if yscroll == ylimit*2 then yscroll = 0 end
		
		if player.charaswap_timer
			if player.charaswap_timer > TICRATE
				player.mo.state = S_PLAY_ROLL
				player.powers[pw_nocontrol] = 2
				
				player.mo.momx = 0
				player.mo.momy = 0
				player.mo.momz = 0
				
				P_SetOrigin(player.mo, player.charaswap.x, player.charaswap.y, player.charaswap.z + player.charaswap.height)
			elseif player.charaswap_timer == TICRATE
				if player.charaswap_accepted
					for i = 45, 360, 45
						local dust = P_SpawnMobjFromMobj(player.charaswap, 0, 0, player.charaswap.height + 32*FRACUNIT, MT_SPINDUST)
						dust.momx = 12*cos(i*ANG1)
						dust.momy = 12*sin(i*ANG1)
					end
						
					player.charaswap = nil
					player.mo.flags = $ & ~MF_NOGRAVITY
					player.mo.momz = 24*FRACUNIT
					
					--S_StartSound(player.mo, sfx_cannon)
					S_StartSound(player.mo, sfx_s3k81)
				else
					player.charaswap = nil
					player.mo.flags = $ & ~MF_NOGRAVITY
				end
				
				player.charaswap_accepted = false
			end
		else
			leftskin = nil
			rightskin = nil
		end
		
		player.charaswap_timer = $ and max(0, $ - 1) or 0
		return
	end
	
	yscroll = min($ + 1, ylimit)
	leftarrowscale = $ and max(0, $ - 1)
	rightarrowscale = $ and max(0, $ - 1)
	
	if player.spectator or player.playerstate return end
	
	local mo = player.mo
	local skinnum = #skins[player.newskin]
	--If we're changing skins, this is the set of instructions we'll use
	local skinchanged = false
	
	local function newskin()
		if not R_SkinUsable(player, skinnum) return end        
		
		player.newskin = skinnum
		S_StartSound(nil, sfx_s3kd2s, player)
		skinchanged = true
	end
	
	leftskin = $ or player.newskin
	rightskin = $ or player.newskin
	
	local cangle = GetRelativeControlDirection(player, 10)
	local dir = 0
	
	if cangle != nil
		cangle = $ - ANGLE_90
		
		for i = 0, 1
			local ang = FixedAngle(180*i*FRACUNIT)
			if min(360*FRACUNIT - AngleFixed(ang - cangle), AngleFixed(ang - cangle)) <= 30*FRACUNIT
				dir = i + 1
				break
			end
		end
	end
	
	if dir != player.charaswap_prevdir
		--Roulette
		if dir == 1
			repeat 
				skinnum = ($ + 1) % #skins
				
				newskin()
			until skinchanged
			
			rightskin = player.newskin
			func = decrease
			
			leftarrowscale = 0
			rightarrowscale = 5
		end
		
		if dir == 2
			rightskin = player.newskin
			
			skinnum = $ - 1
			if skinnum < 0 then skinnum = #skins - 1 end
			
			newskin()
			
			leftskin = player.newskin
			xscroll = 0
			func = increase
			
			leftarrowscale = 5
			rightarrowscale = 0
		end
	end
	
	player.charaswap_prevdir = dir
	
	local charaswap = player.charaswap
	mo.state = S_PLAY_ROLL
	
	local speed = FRACUNIT/2
	local d = R_PointToDist2(0, 0, R_PointToDist2(charaswap.x, charaswap.y, mo.x, mo.y), charaswap.z + charaswap.height - mo.z)
	local dx = d and FixedMul(speed, FixedDiv(charaswap.x - mo.x, d)) or 0
	local dy = d and FixedMul(speed, FixedDiv(charaswap.y - mo.y, d))
	local dz = d and FixedMul(speed, FixedDiv(charaswap.z + charaswap.height - mo.z, d))
	
	P_SetOrigin(mo, charaswap.x + dx, charaswap.y + dy, charaswap.z + charaswap.height + dz)
	
	if yscroll == ylimit
		if (player.cmd.buttons & BT_JUMP) and not (player.buttonhistory & BT_JUMP)
			player.charaswap_timer = 2*TICRATE
			player.charaswap_accepted = true
			
			R_SetPlayerSkin(player, player.newskin)
			S_StartSound(nil, sfx_kc48, player)
			--player.charaswap = nil
		end
			
		if (player.cmd.buttons & BT_SPIN) and not (player.buttonhistory & BT_SPIN)
			player.charaswap_timer = 2*TICRATE
			
			S_StartSound(nil, sfx_kc65, player)
			--player.charaswap = nil
		end
	end
	
	--No control
	player.powers[pw_nocontrol] = 2
	
	--Don't kill me
	player.powers[pw_flashing] = TICRATE
	player.powers[pw_underwater] = max(30*TICRATE, $)
	
	--Update history
	player.buttonhistory = player.cmd.buttons
	
	if func
		local x, y = func(xscroll)
		
		if x
			if xscroll == 0
				leftskin, rightskin = rightskin, leftskin
				xscroll = limit
			end
			
			func = nil
		else
			xscroll = y
		end
	end
end)

local function resify_coord(v, coord, actuallen, baselen)
    local work = (actuallen/v.dupx())
    if (work == baselen)
        return coord
    end
    work = work<<FRACBITS
    coord = FixedMul(coord, work/baselen)
    coord = coord - (work>>1) + (baselen<<(FRACBITS-1))
    return coord
end

local function resify_x(v, coord)
    return resify_coord(v, coord, v.width(), 320)
end

local function resify_y(v, coord)
    return resify_coord(v, coord, v.height(), 200)
end

hud.add(function(v, p)
	if not yscroll or yscroll == ylimit*2 return end
	
	local SCRWIDTH = v.width()
	local SCRHEIGHT = v.height()
	local scrx = SCRWIDTH * FixedDiv(xscroll, limit)
	local scry = SCRHEIGHT * FixedDiv((ylimit - yscroll), ylimit)
	local index = 6
	local ls = skins[leftskin]
	local rs = skins[rightskin]
	
	local lcol = skincolors[ls.prefcolor].ramp[index]
	local rcol = skincolors[rs.prefcolor].ramp[index]
	
	lcol = $ < 100 and ($ < 10 and "00"..$ or "0"..$) or $
	rcol = $ < 100 and ($ < 10 and "00"..$ or "0"..$) or $
	
	v.drawStretched(0, scry, scrx/64, SCRHEIGHT*FRACUNIT/128, v.cachePatch("~"..lcol), V_NOSCALESTART)
	v.drawStretched(scrx, scry, (SCRWIDTH*FRACUNIT - scrx)/64, SCRHEIGHT*FRACUNIT/128, v.cachePatch("~"..rcol), V_NOSCALESTART)
	
	local s = 4*FRACUNIT/3 + sin(FixedAngle(leveltime*FRACUNIT*4))/16
	
	local leftpatch = v.getSprite2Patch(leftskin, SPR2_XTRA, B)
	local rightpatch = v.getSprite2Patch(rightskin, SPR2_XTRA, B)
	
	local patchx = 100*FRACUNIT
	
	v.drawScaled(patchx - leftpatch.width*s/2 - SCRWIDTH*FRACUNIT + scrx - 90*FRACUNIT, scry + 100*FRACUNIT - leftpatch.height*s, 2*s, leftpatch, 0, v.getColormap(TC_BLINK, ls.prefcolor))
	v.drawScaled(patchx - rightpatch.width*s/2 + scrx - 90*FRACUNIT, scry + 100*FRACUNIT - rightpatch.height*s, 2*s, rightpatch, 0, v.getColormap(TC_BLINK, rs.prefcolor))
	
	local arrowdiff = 80*FRACUNIT
	local move = ((leveltime/4)%10)*FRACUNIT
	local las = leftarrowscale and FRACUNIT or 0
	local ras = rightarrowscale and FRACUNIT or 0
	
	v.drawScaled(patchx - arrowdiff + 4*FRACUNIT - move, scry + 102*FRACUNIT, FRACUNIT + las, v.cachePatch("LARROWB"), 0, v.getColormap(leftskin, skincolors[ls.prefcolor].invcolor))
	v.drawScaled(patchx - arrowdiff - move, scry + 100*FRACUNIT, FRACUNIT + las, v.cachePatch("LARROW"), 0, v.getColormap(leftskin, skincolors[ls.prefcolor].invcolor))
	v.drawScaled(patchx + arrowdiff - 4*FRACUNIT + move, scry + 102*FRACUNIT, FRACUNIT + ras, v.cachePatch("LARROWB"), V_FLIP, v.getColormap(leftskin, skincolors[ls.prefcolor].invcolor))
	v.drawScaled(patchx + arrowdiff + move, scry + 100*FRACUNIT, FRACUNIT + ras, v.cachePatch("LARROW"), V_FLIP, v.getColormap(leftskin, skincolors[ls.prefcolor].invcolor))
	
	v.drawScaled(patchx - leftpatch.width*s/2 - SCRWIDTH*FRACUNIT + scrx, scry + 100*FRACUNIT - leftpatch.height*s/2, s, leftpatch)
	v.drawScaled(patchx - rightpatch.width*s/2 + scrx, scry + 100*FRACUNIT - rightpatch.height*s/2, s, rightpatch)
	
	local yesw = v.drawString(240, scry/FRACUNIT + 82, "Confirm")
	local now = v.drawString(240, scry/FRACUNIT + 118, "Cancel")
	
	v.drawScaled(250*FRACUNIT - (string.len("Confirm")*3*FRACUNIT)/2 - 13*FRACUNIT, scry + 82*FRACUNIT, FRACUNIT, v.cachePatch(leveltime/15 & 1 and "BUTTON1" or "BUTTON2"))
	v.drawScaled(250*FRACUNIT - (string.len("Cancel")*3*FRACUNIT)/2 - 12*FRACUNIT, scry + 118*FRACUNIT, FRACUNIT, v.cachePatch(not(leveltime/15 & 1) and "BUTTON3" or "BUTTON4"))
end)