// March of the Machines (V2.0)
// A simple lua script absurdly buffing bots, for masochists looking for a challenge

//-----------------

// Sprites

freeslot(
"SPR_BSTG", // Tag sprites
"MT_BSTG", // Tag object
"S_BSTG" // Tag sprites manager
)

mobjinfo[MT_BSTG] = {
	doomednum = -1,
	spawnstate = S_BSTG,
	dispoffset = 4,
	flags = MF_SCENERY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY
}

states[S_BSTG] = {
	sprite = SPR_BSTG,
	nextstate = S_NULL,
}

// Variables

local mm_enabled = 1
local mm_nbboss = 3
local mm_rb = 16
local mm_bosslist = {}
local mm_marked = 1

// Commands, pretty self explained
// Made only with solo in mind, but should work in netplay too

COM_AddCommand("mm_enable", function(player, var)
    if tonumber(var) == 0 or var == "off"
        CONS_Printf(player, "MM disabled. Bots will hold back from now.")
		mm_enabled = 0
    elseif tonumber(var) == 1 or var == "on"
        CONS_Printf(player, "MM enabled. Good luck.")
		mm_enabled = 1
	elseif tonumber(var) == 2 or var == "hard"
        CONS_Printf(player, "MM Hard Mode enabled. Wait, it can be harder?")
		mm_enabled = 2
	elseif tonumber(var) == 3 or var == "fair"
        CONS_Printf(player, "MM Fair Mode enabled. No huge buffs, only the small changes.")
		mm_enabled = 3
    else
		CONS_Printf(player, "0/off for disable, 1/on for enable, 2/hard for Hard Mode, 3/fair for Fair Mode")
    end
end)

COM_AddCommand("mm_nbbots", function(player, var)
	if tonumber(var) < 1
		var = 1
	end
	mm_nbboss = tonumber(var)
    CONS_Printf(player, "The first "..var.." bots are enhanced. (Will be applied next race)")
end)

COM_AddCommand("mm_rubber", function(player, var)
	if tonumber(var) < 0
		var = 0
	elseif tonumber(var) > 20
		var = 20
	end
	mm_rb = tonumber(var)
    CONS_Printf(player, "Rubberbanding speed for enhanced bots has been set to "..var)
end)

COM_AddCommand("mm_addlist", function(player, var)
	if var != nil
		table.insert(mm_bosslist, var)
		CONS_Printf(player, var.." has been added to the list. (Will be applied next race)")
	else
		CONS_Printf("Please enter a char name in order to add it from the list. (Will be applied next race)")
	end
end)

COM_AddCommand("mm_rmvlist", function(player, var)
	if var != nil
		for i = 1, #mm_bosslist do
			if mm_bosslist[i] == var
				table.remove (mm_bosslist, i)
				CONS_Printf(player, var.." has been removed from the list. (Will be applied next race)")
				break
			end
		end
	else
		CONS_Printf("Please enter a char name in order to remove it from the list. (Will be applied next race)")
	end
end)

COM_AddCommand("mm_seelist", function(player)
	for i = 1, #mm_bosslist do
		CONS_Printf(player, mm_bosslist[i])
	end
end)

COM_AddCommand("mm_indicator", function(player, var)
    if tonumber(var) == 0 or var == "off"
        CONS_Printf(player, "Enhanced bots aren't marked anymore. (Will be applied next race)")
		mm_marked = 0
    elseif tonumber(var) == 1 or var == "on"
        CONS_Printf(player, "Enhanced bots are now marked. (Will be applied next race)")
		mm_marked = 1
    else
		CONS_Printf(player, "0/off for hiding mark, 1/on for showing mark.")
    end
end)

COM_AddCommand("mm_help", function(player)
    CONS_Printf(player, "mm_enable (1/on or 0/off) : Enable or disable this mod. (Default : on)")
	CONS_Printf(player, "mm_nbbots (number) : Number of enhanced bots. (Default : 3)")
	CONS_Printf(player, "mm_rubber (number from 0 to 20) : Enhanced bots rubberbanding speed. (Default : 16)")
	CONS_Printf(player, "mm_addlist (char true name) : Add a character to the list.")
	CONS_Printf(player, "mm_rmvlist (char true name) : Remove a character from the list.")
	CONS_Printf(player, "mm_seelist : Show characters in the list.")
	CONS_Printf(player, "mm_indicator (1/on or 0/off) : Hide or show mark above enhanced bots. (Default : on)")
end)

// A simplified and tweaked version of the AvoidObstacles function used in the SRB2 Kart Player Bots mod
// The goal here is to make this function DRRR compatible and making it less laggy (or at least trying to)
// As for 1.1, the function is renamed ObstacleManager
// Original SRB2 Kart Player Bots mod script by Sonic1983
local function ObstaclesManager(mo, obstacles)
	local player = mo.player
	if obstacles
	and obstacles.valid
	and obstacles.health
	and R_PointToDist2(mo.x, mo.y, obstacles.x, obstacles.y) < 350*mo.scale // Base value : 512*FRACUNIT, using mo.scale because of the mobjscale being different for each map. Reducing detection distance because of lag.
	and P_CheckSight(mo, obstacles)
	// Doing specific obstacles checks here because scanning all obstacles makes it even more laggy
	// *Gru pointing a gun on you* You want to dodge items? Name all item objects
	and (obstacles.type == MT_EGGMANITEM
	or obstacles.type == MT_BANANA
	or obstacles.type == MT_EGGMANITEM_SHIELD
	or obstacles.type == MT_BANANA_SHIELD
	or obstacles.type == MT_ORBINAUT
	or (obstacles.type == MT_ORBINAUT_SHIELD and obstacles.target != mo)
	or obstacles.type == MT_JAWZ
	or (obstacles.type == MT_JAWZ_SHIELD and obstacles.target != mo)
	or obstacles.type == MT_SSMINE
	or obstacles.type == MT_SSMINE_SHIELD
	or obstacles.type == MT_LANDMINE
	or obstacles.type == MT_DUELBOMB
	or obstacles.type == MT_BALLHOG
	or obstacles.type == MT_HYUDORO
	or obstacles.type == MT_HYUDORO_CENTER
	or obstacles.type == MT_DROPTARGET
	or obstacles.type == MT_DROPTARGET_SHIELD
	or obstacles.type == MT_GACHABOM_REBOUND
	or obstacles.type == MT_LSZ_EGGBALL
	or obstacles.type == MT_KART_LEFTOVER
	or obstacles.type == MT_GARDENTOP
	or obstacles.type == MT_BUBBLESHIELD
	or obstacles.type == MT_FALLINGROCK
	// 2.0 new obstacles here
	or obstacles.type == MT_MOTOBUG
	or obstacles.type == MT_GPZ_SEASAW_HITBOX
	or obstacles.type == MT_SPIKEBALL
	or obstacles.type == MT_SMALLMACE
	or obstacles.type == MT_BIGMACE
	or obstacles.type == MT_SMALLFIREBAR
	or obstacles.type == MT_BIGFIREBAR
	or obstacles.type == MT_SNOWMAN
	or (obstacles.type == MT_PLAYER and obstacles != mo and obstacles.player))
		local angle = R_PointToAngle2(0, 0, mo.momx, mo.momy)
		local angle2 = R_PointToAngle2(mo.x, mo.y, obstacles.x, obstacles.y)
		local anglediff = angle - angle2
		if anglediff == 0
			mo.angle = $1 + ANG2
			P_Thrust(mo, mo.angle+ANGLE_90, mo.scale) // Still using mo.scale instead of FRACUNIT. Also increasing base thrust
		elseif anglediff > 0
			mo.angle = $1 + ANG2
			P_Thrust(mo, mo.angle+ANGLE_90, mo.scale)
		elseif anglediff < 0
			mo.angle = $1 - ANG2
			P_Thrust(mo, mo.angle-ANGLE_90, mo.scale)
		end
	end
end

addHook("MapLoad", function(player) // Initialising variables at the start of the race
	local affected = 0
	for player in players.iterate do
		if player.mo != nil and player.mo.valid and player.bot // Reinitialisation
			player.isBoss = 0
		end
	end
	for player in players.iterate do
		if player.mo != nil and player.mo.valid and player.bot and mm_enabled >= 1 // vip list checkup
			for i = 1, #mm_bosslist do
				if mm_bosslist[i] == player.mo.skin
					player.isBoss = 1
					if mm_marked == 1
						local bosstag = P_SpawnMobjFromMobj(player.mo, 0, 0, 0, MT_BSTG) // Tag
						bosstag.target = player.mo
					end
				end
			end
		end
	end
	for player in players.iterate do
		if player.mo != nil and player.mo.valid and player.bot and mm_enabled >= 1 // Are you a bot ?
			if player.isBoss != 1
				player.isBoss = 1
				if mm_marked == 1
					local bosstag = P_SpawnMobjFromMobj(player.mo, 0, 0, 0, MT_BSTG) // Tag
					bosstag.target = player.mo
				end
				affected = $ + 1
				if affected >= mm_nbboss
					break
				end
			end
		end
	end
end)

addHook("ThinkFrame", function(player)
	for player in players.iterate do
		if player.mo != nil and player.mo.valid and player.bot and player.isBoss // Are you a boss bot ?
			// Is the script enabled ?
			if mm_enabled == 1
				// Rubberbanding buff
				player.speedboost = (FRACUNIT/3) + (player.position*FRACUNIT/(21-mm_rb))
				player.handleboost = (FRACUNIT/3) + (player.position*FRACUNIT/7)
				// Double item
				if player.itemtype <= 0 
					player.justHadItem = 0 // Preventing loops
				elseif player.itemtype > 0
				and player.justHadItem == 0
				and player.itemtype != 13 // Double shrink makes the game laggy
				and player.itemtype != 11 // Double SPB is useless
					player.justHadItem = 1
					player.itemamount = $ * 2
				end
				// Random boosts
				if (leveltime > starttime)
				and not player.exiting
					local randomnumber = P_RandomRange(200, 600)
					if (leveltime % randomnumber) == 0
						K_DoSneaker(player, 1)
					end
				end
				// Dodging behavior
				if not player.invincibilitytime
				and not (player.growshrinktimer > 0)
				and player.spinouttimer == 0
				and not player.exiting
				and (leveltime > starttime)
				and P_IsObjectOnGround(player.mo)
					searchBlockmap("objects", ObstaclesManager, player.mo, player.mo.x-RING_DIST*2, player.mo.x+RING_DIST*2, player.mo.y-RING_DIST*2, player.mo.y+RING_DIST*2)
				end
				// Startboost modifications
				if (leveltime < starttime)
					if player.spindash > 3 // Making bots not commiting sudoku with their spindash before the race begins
						player.spindash = 0
					end
				elseif (leveltime == starttime) // Forcing a huge boost at the moment the race begins
					player.sneakertimer = 10
					player.numsneakers = 25
				end
				// Voltage usage
				if player.trickcharge and P_IsObjectOnGround(player.mo)
					P_Thrust(player.mo, player.mo.angle, player.speed/2)
					S_StartSound(player.mo, sfx_gshba)
					player.trickcharge = 0
					player.infinitether = TICRATE*2
				end
			end
			// Did you anger the false gods ?
			if mm_enabled == 2
				// Huge speed buff
				player.speedboost = (FRACUNIT/2) + (player.position*FRACUNIT/(21-mm_rb))
				player.handleboost = (FRACUNIT/3) + (player.position*FRACUNIT/7)
				// Triple item
				if player.itemtype <= 0 
					player.justHadItem = 0
				elseif player.itemtype > 0
				and player.justHadItem == 0
				and player.itemtype != 13 // Imagine 3 shrinks
				and player.itemtype != 11 // No
					player.justHadItem = 1
					player.itemamount = $ * 3
				end
				// Random invincibilities
				if (leveltime > starttime)
				and not player.exiting
					local randomnumber = P_RandomRange(200, 600)
					if (leveltime % randomnumber) == 0
						player.invincibilitytimer = $ + 35
					end
				end
				// Dodging behavior
				if not player.invincibilitytime
				and not (player.growshrinktimer > 0)
				and player.spinouttimer == 0
				and not player.exiting
				and (leveltime > starttime)
				and P_IsObjectOnGround(player.mo)
					searchBlockmap("objects", ObstaclesManager, player.mo, player.mo.x-RING_DIST*2, player.mo.x+RING_DIST*2, player.mo.y-RING_DIST*2, player.mo.y+RING_DIST*2)
				end
				// Absurd startboost modifications
				if (leveltime < starttime)
					if player.spindash > 0
						player.spindash = 0
					end
				elseif (leveltime == starttime) // Yeet
					player.sneakertimer = 10*(16-player.position)
					player.numsneakers = 30
				end
				// Voltage usage
				if player.trickcharge and P_IsObjectOnGround(player.mo)
					P_Thrust(player.mo, player.mo.angle, player.speed/2)
					S_StartSound(player.mo, sfx_gshba)
					player.trickcharge = 0
					player.infinitether = TICRATE*2
				end
			end
			// No bullshit
			if mm_enabled == 3
				// Dodging behavior
				if not player.invincibilitytime
				and not (player.growshrinktimer > 0)
				and player.spinouttimer == 0
				and not player.exiting
				and (leveltime > starttime)
				and P_IsObjectOnGround(player.mo)
					searchBlockmap("objects", ObstaclesManager, player.mo, player.mo.x-RING_DIST*2, player.mo.x+RING_DIST*2, player.mo.y-RING_DIST*2, player.mo.y+RING_DIST*2)
				end
				// Startboost modifications
				if (leveltime < starttime)
					if player.spindash > 3 // Making bots not commiting sudoku with their spindash before the race begins
						player.spindash = 0
					end
				elseif (leveltime == starttime) // Forcing a huge boost at the moment the race begins
					player.sneakertimer = 10
					player.numsneakers = 13
				end
				// Voltage usage
				if player.trickcharge and P_IsObjectOnGround(player.mo)
					P_Thrust(player.mo, player.mo.angle, player.speed/2)
					S_StartSound(player.mo, sfx_gshba)
					player.trickcharge = 0
					player.infinitether = TICRATE*2
				end
			end
		end
	end
end)

// Effect centered
addHook("MobjThinker", function(mo)
	if mo and mo.valid and mo.target
		P_MoveOrigin(mo, mo.target.x, mo.target.y, mo.target.z)
	end
end, MT_BSTG)