--Custom peel-out script inspired by MotdSpork's VL_SuperPeelout-v2.2-- --Created by Kamen Laudo (aka gabruel_0). Feel free to reuse it and/or modify it-- --Adding new states for the player-- freeslot("S_PLAY_PEELOUT1", "S_PLAY_PEELOUT2") --Defining the new states-- states[S_PLAY_PEELOUT1] = { sprite = SPR_PLAY, frame = FF_ANIMATE|SPR2_WALK, tics = -1, action = nil, var1 = 7, var2 = 3, nextstate = S_PLAY_PEELOUT1 } states[S_PLAY_PEELOUT2] = { sprite = SPR_PLAY, frame = FF_ANIMATE|SPR2_DASH, tics = -1, action = nil, var1 = 3, var2 = 2, nextstate = S_PLAY_PEELOUT2 } --Function to update states without reseting them every frame-- local function setNewState(actor, newstate) if actor.state ~= newstate then actor.state = newstate end end --Function to check if player is valid-- local function isPlayerValid(player) return player and player.valid and player.mo and player.mo.valid and player.playerstate == PST_LIVE end --Function to check if object is valid-- local function isMobjValid(mobj) return mobj and mobj.valid end --Function to check if character is transforming-- local function isPlayerTransforming(player) return player.mo.state == S_PLAY_SUPER_TRANS1 or player.mo.state == S_PLAY_SUPER_TRANS2 or player.mo.state == S_PLAY_SUPER_TRANS3 or player.mo.state == S_PLAY_SUPER_TRANS4 or player.mo.state == S_PLAY_SUPER_TRANS5 or player.mo.state == S_PLAY_SUPER_TRANS6 end --Function to check if player is in NiGHTS mode-- local function isPlayerNights(player) return (player.powers[pw_carry] == CR_NIGHTSMODE or player.powers[pw_carry] == CR_NIGHTSFALL) or (maptol & (TOL_NIGHTS|TOL_XMAS)) end --Function to check if the player can perform a peel-out-- local function canPlayerExecutePeelout(player) return P_IsObjectOnGround(player.mo) and player.powers[pw_carry] == CR_NONE and not (player.pflags & PF_FULLSTASIS) and not (player.pflags & PF_SPINNING) and not (player.pflags & PF_SLIDING) and player.mo.state ~= S_PLAY_GASP and player.mo.state ~= S_PLAY_PAIN and player.mo.state ~= S_PLAY_DEAD and player.mo.state ~= S_PLAY_DRWN and player.mo.state ~= S_PLAY_CLING and player.mo.state ~= S_PLAY_CLIMB and not isPlayerTransforming(player) and not P_PlayerInPain(player) and not player.selectingstage and --Hardcoded exception for SRB2 Generations stage select player.exiting == 0 end --Function to spawn dust particles (borrowed from MotdSpork's script)-- local function doPeeloutDust(player) for i = (leveltime % 7) / 2, (leveltime % 7) do local particle = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z + ((player.mo.eflags & MFE_VERTICALFLIP) / MFE_VERTICALFLIP * (player.mo.height - mobjinfo[MT_SPINDUST].height)), MT_SPINDUST) if (player.powers[pw_shield] == SH_ELEMENTAL and not (player.mo.eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))) then particle.state = S_SPINDUST_FIRE1 particle.bubble = false elseif (player.mo.eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)) then particle.state = S_SPINDUST_BUBBLE1 particle.bubble = true else particle.state = S_SPINDUST1 particle.bubble = false end particle.target = player.mo particle.eflags = $|(player.mo.eflags & MFE_VERTICALFLIP) particle.scale = player.mo.scale * 2 / 3 P_SetObjectMomZ(particle, 30 * FRACUNIT / 50 + P_RandomByte() << 10, false) P_InstaThrust(particle, player.mo.angle + (P_RandomRange(-ANG30 / FRACUNIT, ANG30 / FRACUNIT) * FRACUNIT), FixedMul(-30 * FRACUNIT / 12 - 1 * FRACUNIT - P_RandomByte() << 11, player.mo.scale)) P_TryMove(particle, particle.x + particle.momx, particle.y + particle.momy, true) end end --Function to spawn water splashes (borrowed from SRB2's source code and converted to Lua)-- local function doWaterSplash(player) if (player.mo.eflags & (MFE_TOUCHWATER|MFE_TOUCHLAVA)) and not (player.mo.eflags & MFE_UNDERWATER) and leveltime % (TICRATE / 7) == 0 and player.mo.momz == 0 then local splishtype = player.mo.eflags & MFE_TOUCHLAVA and MT_LAVASPLISH or MT_SPLISH local water = P_SpawnMobj(player.mo.x - P_ReturnThrustX(NULL, player.mo.angle, player.mo.radius), player.mo.y - P_ReturnThrustY(NULL, player.mo.angle, player.mo.radius), (player.mo.eflags & MFE_VERTICALFLIP and player.mo.waterbottom - FixedMul(mobjinfo[splishtype].height, player.mo.scale) or player.mo.watertop), splishtype) if player.mo.eflags & MFE_GOOWATER then S_StartSound(water, sfx_ghit) elseif (player.mo.eflags & MFE_TOUCHLAVA) S_StartSound(water, sfx_splash) else S_StartSound(water, sfx_wslap) if player.mo.eflags & MFE_VERTICALFLIP then water.flags2 = $|MF2_OBJECTFLIP water.eflags = $|MFE_VERTICALFLIP end end water.destscale = player.mo.scale P_SetScale(water, player.mo.scale) end end --Function to save peel-out settings-- local function saveSettings(player, value) if isPlayerValid(player) then local cfg = io.openlocal("client/custompeelout/" .. player.mo.skin .. ".cfg", "w+") cfg:write(value) cfg:close() end end --Function to load peel-out settings-- local function loadSettings(player) if isPlayerValid(player) then local cfg = io.openlocal("client/custompeelout/" .. player.mo.skin .. ".cfg", "r") if cfg local num = cfg:read("*n") if num == 1 then player.peeloutenabled = true player.charflags = $|SF_DASHMODE else player.peeloutenabled = false if not (skins[player.mo.skin].flags & SF_DASHMODE) and not player.boostenabled then player.charflags = $ & ~SF_DASHMODE end end cfg:close() else player.peeloutenabled = false end end end --Function to set up the peel-out-- local function setPeelout(player) player.peeloutenabled = true player.charflags = $|SF_DASHMODE end --Function to reset the peel-out-- local function resetPeelout(player) player.peeloutenabled = false if not (skins[player.mo.skin].flags & SF_DASHMODE) and not player.boostenabled then player.charflags = $ & ~SF_DASHMODE end end --Function to enable peel-out for the current character local function enablePeelout(player, arg) if isPlayerValid(player) then setPeelout(player) CONS_Printf(consoleplayer, "Peel-out enabled for " .. player.mo.skin .. ".") if io and player == consoleplayer then saveSettings(player, "1") end else CONS_Printf(consoleplayer, "Can't use this now.") end end --Function to disable peel-out for the current character local function disablePeelout(player, arg) if isPlayerValid(player) then resetPeelout(player) CONS_Printf(consoleplayer, "Peel-out disabled for " .. player.mo.skin .. ".") if io and player == consoleplayer then saveSettings(player, "0") end else CONS_Printf(consoleplayer, "Can't use this now.") end end --Single Player commands-- COM_AddCommand("enablepeelout", enablePeelout) COM_AddCommand("disablepeelout", disablePeelout) --Splitscreen commands-- COM_AddCommand("enablepeelout2", enablePeelout, COM_SPLITSCREEN) COM_AddCommand("disablepeelout2", disablePeelout, COM_SPLITSCREEN) --Reset player attributes after respawn-- addHook("PlayerSpawn", function(player) --Load settings if they exist-- if isPlayerValid(player) then loadSettings(player) player.prevskinp = player.mo.skin --Store the player's current skin in order to check when player changes characters during level end end) --Define how the peel-out should work-- addHook("PlayerThink", function(player) --Check if player is currently playing-- if isPlayerValid(player) and not isPlayerNights(player) then --Only allowed characters can use the peel-out-- if player.peeloutenabled then --Set up custom properties needed for the peel-out to work-- if player.peeloutspeed == nil then player.peeloutspeed = player.normalspeed * 2 end if player.chargingpeelout == nil then player.chargingpeelout = false end if player.peeloutcharge == nil then player.peeloutcharge = 0 * TICRATE end --Check if player can perform a peel-out now-- if canPlayerExecutePeelout(player) then --Activate the skill when pressing the Custom1 button-- if player.cmd.buttons & BT_CUSTOM2 then player.chargingpeelout = true else player.chargingpeelout = false end else player.chargingpeelout = false player.peeloutcharge = 0 end --The peel-out logic itself-- if player.chargingpeelout then --Force the player to stop and play a sound effects-- if player.peeloutcharge == 0 then player.pflags = $|PF_STASIS S_StartSound(player.mo, sfx_cdfm11) end --Change peel-out animation based on how long it's charging-- if player.peeloutcharge < 30 then setNewState(player.mo, S_PLAY_PEELOUT1) else setNewState(player.mo, S_PLAY_PEELOUT2) end --Keep the player in place while peel-out charges-- player.normalspeed = 0 player.peeloutcharge = $ + 1 --Add some particles-- doPeeloutDust(player) doWaterSplash(player) else --Launch the player at max speed if charged for long enough-- if player.peeloutcharge >= 30 then player.cmd.forwardmove = 50 player.normalspeed = player.peeloutspeed setNewState(player.mo, S_PLAY_RUN) player.drawangle = player.mo.angle P_InstaThrust(player.mo, player.mo.angle, FixedMul(player.peeloutspeed, player.mo.scale)) S_StartSound(player.mo, sfx_cdfm01) if player.charflags & SF_DASHMODE then player.dashmode = 3 * TICRATE end elseif player.normalspeed == 0 then --Allow the player to move again-- player.normalspeed = skins[player.mo.skin].normalspeed --Reset player's state if not spinning, dead, or taking damage-- if canPlayerExecutePeelout(player) then setNewState(player.mo, S_PLAY_SKID) end end player.peeloutcharge = 0 end end --Check if player switched characters and reload settings-- if player.prevskinp ~= player.mo.skin then player.prevskinp = player.mo.skin loadSettings(player) --Recalculate Peel-out speed--- if player.peeloutspeed then player.peeloutspeed = player.normalspeed * 2 end end end end) --Tails overlay logic-- addHook("FollowMobj", function(player, followmobj) local tails_x = player.mo.x + P_ReturnThrustX(followmobj, followmobj.angle, FixedMul(-1 * FRACUNIT, followmobj.scale)) local tails_y = player.mo.y + P_ReturnThrustY(followmobj, followmobj.angle, FixedMul(-1 * FRACUNIT, followmobj.scale)) if player.mo.state == S_PLAY_PEELOUT1 then followmobj.state = S_TAILSOVERLAY_STAND followmobj.angle = player.drawangle P_SetOrigin(followmobj, tails_x, tails_y, player.mo.z) return true elseif player.mo.state == S_PLAY_PEELOUT2 then followmobj.state = S_TAILSOVERLAY_RUN followmobj.angle = player.drawangle P_SetOrigin(followmobj, tails_x, tails_y, player.mo.z) return true else return false end end, MT_TAILSOVERLAY) --Metal's jet fume logic-- addHook("FollowMobj", function(player, followmobj) local fume_x = player.mo.x - P_ReturnThrustX(followmobj, player.drawangle, player.mo.radius) local fume_y = player.mo.y - P_ReturnThrustY(followmobj, player.drawangle, player.mo.radius) local fume_z = player.mo.z + (P_GetPlayerHeight(player) >> 1) - (followmobj.height >> 1) if player.mo.state == S_PLAY_PEELOUT1 then if followmobj.state == followmobj.info.spawnstate then followmobj.state = followmobj.info.seestate end P_SetOrigin(followmobj, fume_x, fume_y, fume_z) followmobj.scale = player.mo.scale / 2 followmobj.color = SKINCOLOR_ICY return true elseif player.mo.state == S_PLAY_PEELOUT2 then if followmobj.state == followmobj.info.spawnstate then followmobj.state = followmobj.info.seestate end P_SetOrigin(followmobj, fume_x, fume_y, fume_z) followmobj.scale = player.mo.scale followmobj.color = SKINCOLOR_SUNSET return true else return false end end, MT_METALJETFUME)