-- -- Random Toggle (v1.2 - 5/21/25) -- Written by FrivolousAqua -- -- Background: -- Personally, I suffer from quite a bit of choice paralysis on modded servers because of all of -- the options available to me, both in base and in modded. Heavy Magician just won't cut it -- for playing a random character; I gotta have the game decide for me! -- So, here's this thing! -- -- GAMEPLAY CODE -- -- Net variables local randomizationdone = false -- Is the randomization finished? local charrandomizationvalid = true -- Are we able to randomize characters at all? local colorrandomizationvalid = true -- Are we able to randomize colors at all? local confettispawned = false -- Has the confetti spawned from each player? -- Constants local maxrange = 10 -- What's the maximum range the confetti can have? local maxconfetti = 100 -- How much confetti is dropping? local maxrerolls = 50 -- How many times can you reroll an illegal character? local colorblacklist = {} -- Is this technically a constant...? local characterblacklist = {} -- Eh...? -- AHC_ColorCodeLookup(id) -- Mostly just for cosmetic stuff within the mod. -- Looks up a given skincolor and returns a color code to use in strings. local function AHC_ColorCodeLookup(id) local skinchat = skincolors[id].chatcolor -- this is going to be ugly if skinchat == V_PURPLEMAP then return "\129" elseif skinchat == V_YELLOWMAP then return "\130" elseif skinchat == V_GREENMAP then return "\131" elseif skinchat == V_BLUEMAP then return "\132" elseif skinchat == V_REDMAP then return "\133" elseif skinchat == V_GRAYMAP then return "\134" elseif skinchat == V_ORANGEMAP then return "\135" elseif skinchat == V_SKYMAP then return "\136" elseif skinchat == V_LAVENDERMAP then return "\137" elseif skinchat == V_GOLDMAP then return "\138" elseif skinchat == V_AQUAMAP then return "\139" elseif skinchat == V_MAGENTAMAP then return "\140" elseif skinchat == V_PINKMAP then return "\141" elseif skinchat == V_BROWNMAP then return "\142" elseif skinchat == V_TANMAP then return "\143" end -- Failsafe, also just accounts for white return "\128" end -- AHC_BlacklistColor(color) -- Adds a color to the color blacklist. -- The blacklist prevents unwanted colors from showing up in randomization. -- (Some of these can be REALLY bad to look at...) local function AHC_BlacklistColor(color) local colorcheck = tostring(color) -- Firstly, account for blacklisting Default. -- Since Default is SKINCOLOR_NONE, you gotta make a separate case for it. if colorcheck == "Default" then -- lol. lmao -- Check to see if the skincolor actually exists. -- If it returns SKINCOLOR_NONE, then there's a problem. elseif R_GetColorByName(colorcheck) == SKINCOLOR_NONE then print("\133ERROR:\128 " .. colorcheck .. " does not exist as a color. (Did you make a typo?)") return end colorblacklist[#colorblacklist + 1] = color local colorid = R_GetColorByName(color) print("Added " .. AHC_ColorCodeLookup(colorid) .. color .. "\128 to the color blacklist.") end -- Rawset it so that any external scripts can use it. rawset(_G, "AHC_BlacklistColor", AHC_BlacklistColor) -- AHC_BlacklistCharacter(character) -- Adds a character to the character blacklist. -- The blacklist prevents unwanted characters from showing up in randomization. -- No clue why you'd want to do this since you could just... Not host a character, -- but I might as well put in the functionality for it. local function AHC_BlacklistCharacter(character) local charcheck = string.lower(tostring(character)) -- Does this character exist? -- If it's nil, this means that they are not real. if skins[charcheck] == nil then print("\133ERROR:\128 " .. character .. " does not exist as a character. (Did you make a typo?)") return end characterblacklist[#characterblacklist + 1] = charcheck local charname = skins[charcheck].realname print("Added " .. AHC_ColorCodeLookup(skins[charcheck].prefcolor) .. charname .. "\128 to the character blacklist.") end -- Rawset it so that any external scripts can use it. rawset(_G, "AHC_BlacklistCharacter", AHC_BlacklistCharacter) -- randomchar -- Toggles on or off your random character for the next race. -- TODO: Make it work in menus COM_AddCommand("randomchar", function (p, input) local toggle = string.lower(tostring(input)) if menuactive and (toggle == "on" or toggle == "off") then CONS_Printf(p, "You can only use this command while in a game.") return end -- did you put in an on or off like a good person if toggle == "on" then CONS_Printf(p, "Okay! \131Your character will be randomized\128 the next race.") S_StartSound(nil, sfx_token, p) p.randomchar = true elseif toggle == "off" then CONS_Printf(p, "Okay! \133Your character will no longer randomize\128 every race.") S_StartSound(nil, sfx_token, p) p.randomchar = false -- come on man else CONS_Printf(p, "randomchar: selects a random character for your next race. Accepts on or off.") end end) -- truedefault -- Toggles whether you want your randomization to match your prefcolor. -- Only good way to do this right now, I guess. Can't check to see if you're using default. -- TODO: Make it work in menus COM_AddCommand("truedefault", function (p, input) local toggle = string.lower(tostring(input)) if menuactive and (toggle == "on" or toggle == "off") then CONS_Printf(p, "You can only use this command while in a game.") return end -- did you put in an on or off like a good person if toggle == "on" then CONS_Printf(p, "Okay! \131Your color will be the character's default when randomizing\128 the next race.") S_StartSound(nil, sfx_token, p) p.truedefault = true elseif toggle == "off" then CONS_Printf(p, "Okay! \133Your color will not match the character's default when randomizing\128 every race.") S_StartSound(nil, sfx_token, p) p.truedefault = false -- come on man else CONS_Printf(p, "truedefault: whether or not your color defaults to the skin's prefcolor when randomized. Accepts on or off.") end end) -- randomcolor -- Toggles whether you want your color to be randomized every race. -- TODO: Make it work in menus COM_AddCommand("randomcolor", function (p, input) local toggle = string.lower(tostring(input)) if menuactive and (toggle == "on" or toggle == "off") then CONS_Printf(p, "You can only use this command while in a game.") return end -- did you put in an on or off like a good person if toggle == "on" then CONS_Printf(p, "Okay! \131Your color will be randomized\128 the next race.") S_StartSound(nil, sfx_token, p) p.randomcolor = true elseif toggle == "off" then CONS_Printf(p, "Okay! \133Your color will not be randomized\128 every race.") S_StartSound(nil, sfx_token, p) p.randomcolor = false -- come on man else CONS_Printf(p, "randomcolor: selects a random color for your next race. Accepts on or off.") end end) -- AHC_ValidRandomChar(p) -- Randomizes the current user's character. -- Keeps randomizing until it finds a usable skin. This is to account for unlockables. local function AHC_ValidRandomChar(p) -- For loop to determine what character you should get for i = 1, maxrerolls -- Failsafe -- If you can't roll any of these guys at all (due to unlocks or something else), just roll Knuckles if i == maxrerolls then R_SetPlayerSkin(p, "knuckles") p.validforconfetti = true break end -- Check here what skin you get local randomskin = P_RandomRange(0, #skins - 1) local illegal = false -- Check if it's on the blacklist; if it's not, keep it for i, j in ipairs(characterblacklist) do if skins[randomskin].name == j then -- CONS_Printf(p, "ILLEGAL CHARACTER ENCOUNTERED: " .. skins[randomskin].name) illegal = true end end -- Validate the usability, and set it if it is usable if R_SkinUsable(p, randomskin) and illegal == false then R_SetPlayerSkin(p, randomskin) p.validforconfetti = true break end end end -- AHC_ValidRandomColor(p) -- Randomizes the current user's color. -- Keeps randomizing until it finds a valid color. SOme colors may be blacklisted. local function AHC_ValidRandomColor(p) -- infinite loop, hoping the game doesn't get stuck while true do -- Check here what color you get local randomcolor = P_RandomRange(1, #skincolors - 1) local illegal = false -- Does the blacklist even exist? If not, just apply it and move on if #colorblacklist == 0 then p.skincolor = randomcolor p.validforconfetti = true break end -- Otherwise... -- Check if it's on the blacklist; if it's not, keep it for i, j in ipairs(colorblacklist) do if R_GetNameByColor(randomcolor) == j then -- CONS_Printf(p, "ILLEGAL COLOR ENCOUNTERED: " .. skincolors[randomcolor].name) illegal = true end end -- If it hasn't been marked as illegal, then we're good to go! -- Otherwise, go through the loop again! if illegal == false then -- CONS_Printf(p, "USING: " .. skincolors[randomcolor].name) p.skincolor = randomcolor p.validforconfetti = true break end end end -- AHC_RandomizeInit(p) -- Initializes some things when starting up a match. local function AHC_RandomizeInit(p) if p.bot then return end -- Bots can't become random characters like a player can! -- p.realskincolor = CV_FindVar("color").value - Not netsynced p.oldrandomchar = p.randomchar p.oldrandomcolor = p.randomcolor p.oldtruedefault = p.truedefault p.validforconfetti = false -- lol??? p.roulettesound = 0 -- Randomize before the match. if p.oldrandomchar == true and charrandomizationvalid == true then AHC_ValidRandomChar(p) end -- Randomize the player's skincolor if they opted into it if p.oldrandomcolor == true and colorrandomizationvalid == true then AHC_ValidRandomColor(p) end end -- AHC_RandomizeChar(p) -- Function for randomizing your character next race. local function AHC_RandomizeChar(p) -- Don't run the function if we're not randomizing in the first place if p.oldrandomchar != true and p.oldrandomcolor != true then return end -- (Also, do some sanity checks) if not (p and p.valid) then return end if p.spectator then return end if not (p.mo and p.mo.valid) then return end -- I FUCKING LOVE NIL CHECKS p.roulettesound = p.roulettesound and $ or 0 -- Randomization starts here; -- Randomizes every few tics ending on a random result. if (p.oldrandomchar == true or p.oldrandomcolor == true) -- If you want your character to be randomized... Or, color! and leveltime % 3 == 1 -- So it doesn't change every frame... and leveltime < starttime / 4 then -- If the leveltime is less than the starttime divided by 2... -- CONS_Printf(p, "Am I valid for confetti? " .. tostring(p.validforconfetti)) -- Be sure to ONLY randomize if there are valid skins to pull from. if p.oldrandomchar == true and charrandomizationvalid == true then AHC_ValidRandomChar(p) end -- Recolor the player's color accordingly to the prefcolor -- Wish I could make it account for SKINCOLOR_NONE (default). Seems a little impossible atm. if p.oldtruedefault == true and charrandomizationvalid == true then p.skincolor = skins[p.skin].prefcolor end -- Randomize the player's skincolor if they opted into it if p.oldrandomcolor == true and colorrandomizationvalid == true then AHC_ValidRandomColor(p) end -- goofiest variable name ive written methinks if p.validforconfetti == true then S_StartSound(nil, sfx_itrol1 + p.roulettesound, p) -- Play a roulette sound end p.roulettesound = $ + 1 if p.roulettesound > 7 then p.roulettesound = 0 end end -- Randomization is done! -- Play a little sound to celebrate it (along with the character's slow line) if randomizationdone == true and confettispawned == true and p.validforconfetti == true then S_StartSound(nil, sfx_itrolk, p) S_StartSound(nil, sfx_s3k3d, p) S_StartSound(nil, skins[p.skin].soundsid[SKSKSLOW], p) -- Let's spawn some confetti to celebrate. for i = 1, maxconfetti -- Where are we spawning it, what's the state? local confetti = P_SpawnMobjFromMobj(p.mo, 0, 0, 0, MT_THOK) confetti.state = S_GNFT -- What's the color? if p.oldrandomcolor == true then confetti.color = P_RandomRange(0, #skincolors - 1) else confetti.color = p.mo.color end confetti.colorized = true -- How long does it last for? confetti.fuse = TICRATE confetti.destscale = 0 confetti.scalespeed = p.mo.scale / TICRATE -- Go with the player's momentum! confetti.momx = (P_RandomRange(maxrange * -1, maxrange) * p.mo.scale) + p.mo.momx confetti.momy = (P_RandomRange(maxrange * -1, maxrange) * p.mo.scale) + p.mo.momy confetti.momz = (P_RandomRange(2, maxrange) * p.mo.scale) + p.mo.momz end end end -- Hook for the randomization function addHook("PlayerThink", function (p) AHC_RandomizeChar(p) end) -- Do some pre-game prep addHook("MapLoad", function () randomizationdone = false charrandomizationvalid = true colorrandomizationvalid = true -- Check to see if the skin count is equal to the count in the blacklist. -- This indicates that whoever is hosting has filled up the entire blacklist with the current cast. if #skins == #characterblacklist then print("\133ERROR:\128 No valid skins to randomize to!? Character randomization will not occur this round.") charrandomizationvalid = false end -- The same goes for skincolors. -- The only color that cannot be checked for is default. if #skincolors - 1 == #colorblacklist then print("\133ERROR:\128 No valid colors to randomize to!? Color randomization will not occur this round.") colorrandomizationvalid = false end -- Loop through each player and initialize them accordingly for p in players.iterate AHC_RandomizeInit(p) end end) -- Simple thinkframe that only runs if the leveltime is = to starttime / 4 addHook("ThinkFrame", function () if leveltime >= starttime / 4 then randomizationdone = true else randomizationdone = false end if leveltime == starttime / 4 then confettispawned = true else confettispawned = false end end) -- make sure shit is netsynced addHook("NetVars", function(net) randomizationdone = net($) charrandomizationvalid = net($) colorrandomizationvalid = net($) confettispawned = net($) end) -- -- HUD CODE -- local function whoamiracingas(v, p) -- No need to bother running this if you're using a set character -- ...and also if you're using a set color! if (p.oldrandomchar != true or charrandomizationvalid != true) and (p.oldrandomcolor != true or colorrandomizationvalid != true) then return end local maxdisplaytime = (starttime / 4) + (TICRATE * 3) -- ...Also, don't run this if we're done randomizing after some time if leveltime > maxdisplaytime then return end local skinname = skins[p.skin].realname -- Shows the current skin name. local randommessage = "Randomizing." -- Dot effect. Yummy. if (leveltime / 8) % 3 == 1 then randommessage = "Randomizing.." elseif (leveltime / 8) % 3 == 2 then randommessage = "Randomizing..." end -- Draws a message at the bottom. -- If we're currently randomizing, display the randomization message. if randomizationdone == false then v.drawString(160, 180, randommessage, V_SNAPTOBOTTOM, "thin-center") -- Otherwise, inform the player of what they're playing as. else local colorname = skincolors[p.skincolor].name -- Shows the current color name. if leveltime < (maxdisplaytime - TICRATE + 17) or leveltime % 2 then v.drawString(160, 170, "Your character is " .. AHC_ColorCodeLookup(skins[p.skin].prefcolor) .. skinname .. "\128!", V_SNAPTOBOTTOM, "thin-center") v.drawString(160, 180, "(Current color is " .. AHC_ColorCodeLookup(p.skincolor) .. colorname .. "\128.)", V_SNAPTOBOTTOM, "thin-center") v.drawString(160, 190, "Good luck!", V_SNAPTOBOTTOM, "thin-center") end end end hud.add(whoamiracingas, "game")