PWADí</* Wall Run version 1.1.0 by Kaldrum This script adds wall running functionality when a player leaves a halfpipe, giving them either a wallrunning or wallrolling state. When in either state, the player can walljump to gain a burst of horizontal momentum away from the wall, and can use their ability following this jump. In the running state, the player is able to freely run on the wall, with inputs down increasing downwards momentum and inputs upwards partially counteracting gravity, allowing for extended time on the wall. The player is able to transition into their wallrolling state at any time as long as they are currently touching the wall. */ if WallRun return end rawset(_G, "WallRun", true) freeslot( "S_PLAY_WALLRUN", "S_PLAY_WALLROLL" ) local DASHMODE_THRESHOLD = TICRATE * 3 local DASHMODE_MAX = TICRATE * 3 + 3 local multiwallrun = false //Needs different animations than default ones. states[S_PLAY_WALLRUN] = { sprite = SPR_PLAY, frame = SPR2_RUN, tics = 3, nextstate = S_PLAY_RUN } states[S_PLAY_WALLROLL] = { sprite = SPR_PLAY, frame = SPR2_ROLL, tics = 3, nextstate = S_PLAY_ROLL } //Obtains the object's angle required to face directly towards a wall. Accounts for both front and back sidedefs. local function GetWallAngle(mo, line) local mobjAngle = R_PointToAngle2(line.v1.x, line.v1.y , line.v2.x, line.v2.y) if(#line.frontsector == #mo.subsector.sector) mobjAngle = $ + ANGLE_90 mo.lineside = 0 elseif (line.backsector ~= nil) and (#line.backsector == #mo.subsector.sector) mobjAngle = $ - ANGLE_90 mo.lineside = 1 else //just in case mobjAngle = $ + ANGLE_90 mo.lineside = 0 end return mobjAngle end //source code but wallrun also allows a jetfume to appear. slightly offset when rollangle makes the player face upside down local function P_DoMetalJetFume(player, fume) local FUME_SKINCOLORS = { SKINCOLOR_ICY, SKINCOLOR_SKY, SKINCOLOR_CYAN, SKINCOLOR_WAVE, SKINCOLOR_TEAL, SKINCOLOR_AQUA, SKINCOLOR_SEAFOAM, SKINCOLOR_MINT, SKINCOLOR_PERIDOT, SKINCOLOR_LIME, SKINCOLOR_YELLOW, SKINCOLOR_SANDY, SKINCOLOR_GOLD, SKINCOLOR_APRICOT, SKINCOLOR_SUNSET } local mo = player.mo local angle = player.drawangle local rollangle = mo.rollangle local dist = 0 local heightoffset = 0 if (mo.eflags & MFE_VERTICALFLIP) heightoffset = mo.height - (P_GetPlayerHeight(player)>>1) else heightoffset = (P_GetPlayerHeight(player)>>1) end local dashmode = min(player.dashmode, DASHMODE_MAX) local underwater = mo.eflags & MFE_UNDERWATER if (mo.state ~= S_PLAY_WALLRUN and player.panim ~= PA_WALK and player.panim ~= PA_RUN and player.panim ~= PA_DASH) if fume.state ~= fume.info.spawnstate fume.state = fume.info.spawnstate end return end if underwater fume.movedir = $ + FixedAngle(FixedDiv(2*player.speed, 3 * mo.scale)) fume.movefactor = $ + player.speed if fume.movefactor > FixedDiv(2*player.normalspeed, 3 *mo.scale) local i = 0 local radiusv = 4*FRACUNIT local radiusx = P_ReturnThrustX(mo, angle, -mo.radius) local radiusy = P_ReturnThrustY(mo, angle, -mo.radius) local factorx = P_ReturnThrustX(mo, angle + ANGLE_90, mo.scale) local factory = P_ReturnThrustY(mo, angle + ANGLE_90, mo.scale) local offseth = 0 local offsetv = 0 local x = 0 local y = 0 local z = 0 for i = -1, 1, 2 do offseth = i*P_ReturnThrustX(fume, fume.movedir, radiusv) offsetv = i*P_ReturnThrustY(fume, fume.movedir, radiusv) x = mo.x + radiusx + FixedMul(offseth, factorx) y = mo.y + radiusy + FixedMul(offseth, factory) z = mo.z + heightoffset + offsetv P_SpawnMobj(x, y, z, MT_SMALLBUBBLE).scale = mo.scale>>1 end fume.movefactor = 0 end if player.panim == PA_WALK if fume.state ~= fume.info.spawnstate fume.threshold = 0 fume.state = fume.info.spawnstate end return end end if(fume.state == fume.info.spawnstate) fume.state = fume.info.seestate end if (dashmode > DASHMODE_THRESHOLD) and (fume.state ~= fume.info.seestate) fume.destscale = mo.scale fume.flags2 = $ & ~ MF2_DONTDRAW fume.flags2 = $ | (mo.flags2 & MF2_DONTDRAW) else if dashmode == DASHMODE_THRESHOLD and dashmode > fume.movecount fume.state = fume.info.seestate P_SetScale(fume, mo.scale << 1) end fume.flags2 = (fume.flags2 & ~ MF2_DONTDRAW) | (mo.flags2 & MF2_DONTDRAW) fume.destscale = mo.scale + FixedDiv(player.speed, player.normalspeed) if underwater fume.destscale = $/6 else fume.destscale = $/3 end fume.color = FUME_SKINCOLORS[(dashmode * table.maxn(FUME_SKINCOLORS) / (DASHMODE_MAX + 1))+1] if underwater fume.frame = fume.frame & FF_FRAMEMASK | FF_ANIMATE | (P_RandomRange(0,9) * FF_TRANS10) fume.threshold = 1 elseif fume.threshold fume.frame = (fume.frame & FF_FRAMEMASK) | fume.state.frame fume.threshold = 0 end end fume.movecount = dashmode fume.flags2 = (fume.flags2 & ~ MF2_OBJECTFLIP) | (mo.flags2 & MF2_OBJECTFLIP) fume.eflags = (fume.eflags & ~ MFE_VERTICALFLIP) | (mo.eflags & MFE_VERTICALFLIP) dist = -mo.radius - FixedMul(fume.info.radius, fume.destscale - mo.scale/3) //gets offset from the player sprite when rolled fume.rollangle = rollangle local x = mo.x + P_ReturnThrustX(fume, angle, dist) local y = mo.y + P_ReturnThrustY(fume, angle, dist) local z = mo.z + heightoffset - (fume.height>>1) P_TeleportMove(fume, x, y, z) if player.normalspeed >= player.normalspeed * 2 P_SpawnGhostMobj(fume) end end addHook("FollowMobj", function(player, fume) if not player.mo.skin == "metalsonic" return end P_DoMetalJetFume(player, fume) return true end, MT_METALJETFUME) /* If touching a wall following a halfpipe launch: Gets the player angle of the wall the player is touching. Checks if the "wall" is a thok barrier showing a skybox instead of wall texture. If so, the player cannot enter a wallrun state. Checks the difference between the previous wall angle and the current one. If this difference is 45 degrees or under, sets the wallangle to the new one. If not, ignores it. This prevents the sides of sectors from rotating the player 90 degrees when they move between them. If there is no angle stored, stores the current wall angle. Sets the proper state depending on how the player launches from the slope. Resets the wallrun state loss timer. */ addHook("MobjMoveBlocked", function(mo, object, line) if not mo.player return end if mo.player.wallruntype ~= 0 local lineAngle = GetWallAngle(mo, line) if mo.lineside == 0 if line.backsector ~= nil if(mo.z > line.backsector.ceilingheight) and ((line.frontside.toptexture == 0) or (line.backsector.ceilingpic == "F_SKY1")) return elseif(mo.z < line.backsector.floorheight) and ((line.frontside.bottomtexture == 0) or (line.backsector.floorpic == "F_SKY1")) return elseif (line.flags & ML_IMPASSIBLE) and (mo.z>line.backsector.floorheight) and (mo.z line.frontsector.ceilingheight) and (line.backside.toptexture == 0) return elseif(mo.z < line.frontsector.floorheight) and ((line.backside.bottomtexture == 0) or (line.frontsector.floorpic == "F_SKY1")) return elseif (line.flags & ML_IMPASSIBLE) and (mo.z>line.frontsector.floorheight) and (mo.z 0 mo.rollangle = FixedAngle(FRACUNIT * player.turninput * -90 / 50) elseif player.forwardinput < 0 mo.rollangle = ANGLE_180 + FixedAngle(FRACUNIT * player.turninput * 90 / 50) end else if player.forwardinput > 0 mo.rollangle = 0 player.drawangle = player.wallangle //this is meant to correct sprites facing the incorrect direction when reversed, however looks weird with models for some reason elseif player.forwardinput < 0 mo.rollangle = ANGLE_180 player.drawangle = player.wallangle + ANGLE_180 end end end //Wall spin, should add more character exclusions at some point to increase compatibility with other mods if (player.cmd.buttons & BT_SPIN) and (player.wallrun == 1) and not (mo.skin == "fang" or mo.skin == "amy") mo.state = S_PLAY_WALLROLL player.wallruntype = 2 player.pflags = $ | PF_SPINNING S_StartSound(mo, 17) mo.rollangle = 0 end end // Not entirely sure if this is the best way to keep a rolling player on the wall, the slight forward movement prevents them from landing back onto the slope without extra input. if mo.state == S_PLAY_WALLROLL P_Thrust(mo, player.wallangle, FRACUNIT/128) player.drawangle = player.wallangle end //Walljump button handling if (player.cmd.buttons & BT_JUMP) and player.wjready == true player.wjready = false player.walljump = true elseif (player.cmd.buttons & BT_JUMP) player.wjready = false player.walljump = false elseif mo.state == S_PLAY_WALLRUN player.wjready = true player.walljump = false elseif mo.state == S_PLAY_WALLROLL player.wjready = true player.walljump = false else player.wjready = false player.walljump = false end //Walljump action, ends wallrun and ability to enter wallrun. Allows secondary jump actions. if player.walljump == true P_InstaThrust(mo, player.wallangle + ANGLE_180, 20*FRACUNIT) mo.state = S_PLAY_JUMP S_StartSound(mo, 14) player.pflags = $ | PF_JUMPED player.pflags = $ & ~ PF_THOKKED //multiwallrun: if enabled, prevents the immediate wall run state loss from wall jumping. also rotates the player. if multiwallrun == true mo.angle = player.wallangle + ANGLE_180 player.stoptimer = true if (twodlevel or mo.flags2 & MF2_TWOD) player.wrinputdir = -$ end player.wallruntype = 1 else player.wallruntype = 0 end end //If the player is off of the wall for 15 tics, then they cannot reenter a wallrunning state. if (player.wallrun == 0) and (player.exitwallrun < 15) and (player.stoptimer ~= true) player.exitwallrun = $ + 1 end if player.wallrun == 1 player.wallrun = 2 end if player.exitwallrun == 15 player.wallruntype = 0 player.exitwallrun = 16 end //kinda lame implementation of this, will actually fix it later if player.charflags & SF_DASHMODE if player.dashmode > DASHMODE_THRESHOLD player.wasdashmode = true elseif player.dashmode < 84 player.wasdashmode = false end end end) //Saves the players inputs for use in wall movement, but prevents them from acutally changing the player's direction. addHook("PreThinkFrame", function() for player in players.iterate do local mo = player.mo if mo.state == S_PLAY_WALLRUN if not(twodlevel or mo.flags2 & MF2_TWOD) player.forwardinput = player.cmd.forwardmove * player.wrinputdir player.turninput = player.cmd.sidemove * player.wrinputdir if (player.cmd.forwardmove ~= 0) and (player.forwardinput < 0) and (player.inputFlip == false) player.cmd.forwardmove = 0 end player.cmd.sidemove = 0 else //uses sidemove instead of forwardmove in 2d mode. disables sideways wall running player.forwardinput = player.cmd.sidemove * player.wrinputdir if (player.cmd.sidemove ~= 0) and (player.forwardinput < 0) and (player.inputFlip == false) player.cmd.sidemove = 0 end end elseif mo.state == S_PLAY_WALLROLL if not(twodlevel or mo.flags2 & MF2_TWOD) player.cmd.forwardmove = 0 else player.cmd.sidemove = 0 end end end end) addHook("MapChange", function(map) //add "Lua.multiwallrun" to a level header to enable subsequent wall runs following a walljump if mapheaderinfo[map].multiwallrun == "active" multiwallrun = true else multiwallrun = false end end) addHook("NetVars", function(net) multiwallrun = net(multiwallrun) end) /*TO DO: *add actual animations for wall running *model support would be easier, just rotation of existing running ones, but new spriting would also be necessary *animation speed should also be based on z momentum *maybe figure out a way to keep wallroll touching the wall without adding extra forward momentum so manual adjustment isn't required to land back in the same place when launching straight up *probably more stuff idk */ á<LUA_WLRN