-- Teleport (tailsdoll) — SAFE ONLY, LEFT/RIGHT ONLY
-- Keys:
--   BT_WEAPONPREV = left teleport
--   BT_WEAPONNEXT = right teleport

addHook("PlayerThink", function(player)
	if not player.mo or (maptol & TOL_NIGHTS) then return end

	-- Disable in multiplayer/netplay/splitscreen to avoid conflicts with weapon selection
	if multiplayer or netgame or splitscreen then return end

	-- cooldown
	if not player.teleport_cooldown then
		player.teleport_cooldown = 0
	elseif player.teleport_cooldown > 0 then
		player.teleport_cooldown = $ - 1
	end

	-- optional re-enable from other systems
	if player.infinite then
		if not player.infinite.teleport_reenable then
			player.infinite.teleport_reenable = 0
		elseif player.infinite.teleport_reenable > 0 then
			player.infinite.teleport_reenable = $ - 1
			if player.infinite.teleport_reenable == 0 then
				player.infinite.snap_available = false
			end
		end
	end

	-- Basic checks
	if not player.valid or player.playerstate ~= PST_LIVE then return end
	if player.mo.skin ~= "sonic" then return end

	-- Decide teleport direction (LEFT/RIGHT ONLY)
	local moveangle = nil
	if (player.cmd.buttons & BT_WEAPONPREV) ~= 0 then
		moveangle = player.mo.angle + ANGLE_90
	elseif (player.cmd.buttons & BT_WEAPONNEXT) ~= 0 then
		moveangle = player.mo.angle - ANGLE_90
	end

	if not moveangle
	or player.teleport_cooldown > 0
	or (player.infinite and player.infinite.snap_available)
	then
		return
	end

	local step = 16*FRACUNIT
	local max_distance = 100*FRACUNIT
	local distance = 0

	-- Save original position
	local ox, oy, oz = player.mo.x, player.mo.y, player.mo.z

	while distance < max_distance do
		local dx = FixedMul(step, cos(moveangle))
		local dy = FixedMul(step, sin(moveangle))
		local nx = player.mo.x + dx
		local ny = player.mo.y + dy

		if P_TryMove(player.mo, nx, ny)
		and player.mo.z >= player.mo.floorz
		and (player.mo.z + player.mo.height) <= player.mo.ceilingz
		then
			P_SetOrigin(player.mo, nx, ny, player.mo.z)
			distance = $ + step

			-- brush-through damage
			searchBlockmap("objects", function(_, found)
				if found and found.valid
				and (found.flags & MF_SHOOTABLE)
				and not found.player
				then
					P_DamageMobj(found, player.mo, player.mo, 6*FRACUNIT)
				end
			end, player.mo)
		else
			break
		end
	end

	-- finalize if we moved at all
	if distance > 0 then
		local dx = player.mo.x - ox
		local dy = player.mo.y - oy
		local dz = player.mo.z - oz

		player.mo.momx = player.mo.momx
		player.mo.momy = player.mo.momy
		player.mo.momz = 0

		player.pflags = $ | PF_THOKKED
		player.pflags = $ & ~PF_SPINNING
		player.mo.state = S_PLAY_FALL

		player.mo.flags2 = $ | MF2_DONTDRAW
		S_StartSound(player.mo, sfx_appear)

		local ghost = P_SpawnGhostMobj(player.mo)
		if ghost and ghost.valid then
			ghost.color = SKINCOLOR_COBALT
			ghost.colorized = true
			ghost.fuse = 15
		end

		player.powers[pw_flashing] = 10
		player.teleport_cooldown = 10

		-- shift crystal + parts by the same delta
		local function shift(mo, sx, sy, sz)
			if mo and mo.valid then
				P_TeleportMove(mo, mo.x + sx, mo.y + sy, mo.z + sz)
			end
		end

		local crystal = player.followmobj
		if crystal and crystal.valid then
			shift(crystal, dx, dy, dz)
			if crystal.tailsdollparts then
				for _, part in ipairs(crystal.tailsdollparts) do
					shift(part, dx, dy, dz)
				end
			end
		end
	else
		-- failed: snap back and play appear
		P_SetOrigin(player.mo, ox, oy, oz)
		if not S_SoundPlaying(player.mo, sfx_appear) then
			S_StartSound(player.mo, sfx_appear)
		end
		player.mo.flags2 = $ & ~MF2_DONTDRAW
	end
end)
