//Freeslot block
freeslot("SFX_HMGTHK",
"SFX_MSCHRG",
"MT_HOMINGCRUSH",
"MT_PLAYMETALAURA",
"MT_PLAYMETALORB",
"MT_GREATDIVIDE1MARK",
"MT_CRYSTALSNAILER",
"MT_TAILSARROWS"
)
//Gravity/scale multiplier for vertical momentum shifts
local function P_GravAndScale (actor)
return P_MobjFlip(actor)*actor.scale
end
//Check if a player is not in some kind of special state and is ready to perform an action
local function P_PlayerActionCheck (player)
if not (player.pflags & PF_NIGHTSMODE)
and not (player.pflags & PF_ROPEHANG)
and not (player.pflags & PF_MACESPIN)
and not (player.pflags & PF_SLIDING)
and not (player.pflags & PF_CARRIED)
and not (player.pflags & PF_STASIS)
and not (player.pflags & PF_JUMPSTASIS)
and not (player.exiting)
and (player.powers[pw_nocontrol] == 0)
and not (player.mo.tracer and player.mo.tracer.type == MT_TUBEWAYPOINT)
and not ((player.mo.state >= S_PLAY_SUPERTRANS1) and (player.mo.state <= S_PLAY_SUPERTRANS9))
and not (player.mo.state == S_PLAY_PAIN)
and not (player.mo.state == S_PLAY_CARRY) then
return true
else
return false
end
end
//Check if a player is ready to perform an action that involves pressing spin
local function P_SpinActionCheck (player)
if (P_PlayerActionCheck(player) == true)
and not ((P_IsObjectOnGround(player.mo) == false)
and ((player.powers[pw_shield] == SH_JUMP)
or ((player.powers[pw_super] != 0) and (player.charability == CA_FLY)))) then
return true
else
return false
end
end
//Return a proper color value for an object to be spawned by a player
local function P_PlayerColorSpawn (player)
if player.powers[pw_super] != 0 then
return SKINCOLOR_YELLOW
else
return player.mo.color
end
end
//Spawn a thok trail from a plyer's mobj
local function P_PlayerThokTrail (player)
//Spawn thok object
local thok = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_THOK)
//Set its various parameters to match the player
thok.color = P_PlayerColorSpawn(player)
thok.target = player.mo
thok.scale = player.mo.scale
if player.mo.eflags & MFE_VERTICALFLIP then
thok.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
end
thok.z = $1 - FixedMul(16*FRACUNIT, P_GravAndScale(thok))
end
//Find a target enemy for Metal Sonic
local function P_MetalTargetLook (player, dist, allaround)
//Initialize player target variable
local playertarg = nil
local targdist = FixedMul(dist, player.mo.scale)
//Begin searching through mobjs
for mobj in thinkers.iterate("mobj")
//Determine angle difference between this object and the player
local angdiff = R_PointToAngle2(player.mo.x, player.mo.y, mobj.x, mobj.y) - player.mo.angle
//Are they an appropriate target?
if (mobj.health > 0) //Mobj is alive
and ((mobj.flags & MF_ENEMY) //Mobj is an enemy
or (mobj.flags & MF_BOSS)) //or a boss
and (mobj.flags & MF_SHOOTABLE) //Mobj is killable
and not (mobj.flags2 & MF2_FRET) //Mobj is not fretting, whatever that means
and not (((twodlevel) or (player.mo.flags2 & MF2_TWOD))
and (abs(player.mo.y-mobj.y) > player.mo.radius)) //Object isn't outside of player's 2D range
and not (P_AproxDistance(P_AproxDistance(player.mo.x-mobj.x, player.mo.y-mobj.y),
player.mo.z-mobj.z) > FixedMul(dist,player.mo.scale)) //Mobj is not too far away
and not (((angdiff > ANGLE_45) or (angdiff < -ANGLE_45)) and (allaround == false)) //Object is not behind player's back
and (P_CheckSight(player.mo, mobj) == true) then //Objects can 'see' each other
if not (P_AproxDistance(P_AproxDistance(player.mo.x-mobj.x, player.mo.y-mobj.y),
player.mo.z-mobj.z) > targdist) then //If object is not too far away to become nearest target
//Set object as nearest target
playertarg = mobj
targdist = P_AproxDistance(P_AproxDistance(player.mo.x-mobj.x, player.mo.y-mobj.y), player.mo.z-mobj.z)
end
end
end
//Return the target variable
return playertarg
end
//Kills all of the enemies within a certain radius of Metal Sonic than he can see
local function P_MetalKillEverything (player, dist)
//Initialize maximum distance variable
local targdist = FixedMul(dist, player.mo.scale)
//Begin searching through mobjs
for mobj in thinkers.iterate("mobj")
//Are they an appropriate target?
if (mobj.health > 0) //Mobj is alive
and ((mobj.flags & MF_ENEMY) //Mobj is an enemy
or (mobj.flags & MF_BOSS)) //or a boss
and (mobj.flags & MF_SHOOTABLE) //Mobj is killable
and not (mobj.flags2 & MF2_FRET) //Mobj is not fretting, whatever that means
and not (((twodlevel) or (player.mo.flags2 & MF2_TWOD))
and (abs(player.mo.y-mobj.y) > player.mo.radius)) //Object isn't outside of player's 2D range
and not (P_AproxDistance(P_AproxDistance(player.mo.x-mobj.x, player.mo.y-mobj.y),
player.mo.z-mobj.z) > FixedMul(dist,player.mo.scale)) //Mobj is not too far away
and (P_CheckSight(player.mo, mobj) == true) then //Objects can 'see' each other
if not (P_AproxDistance(P_AproxDistance(player.mo.x-mobj.x, player.mo.y-mobj.y),
player.mo.z-mobj.z) > targdist) then //If object is not too far away to become nearest target
//VileAttack the object and return the player's target to normal afterwards
local oldtarget = player.mo.target
local oldmomz = mobj.momz
player.mo.target = mobj
A_VileAttack(player.mo, 0, MT_CYBRAKDEMON_VILE_EXPLOSION)
//If mobj is not dead yet, prevent it from being thrust upwards
if mobj.health > 0 then
mobj.momz = oldmomz
end
player.mo.target = oldtarget
end
end
end
end
//Axis742D (Axis2D 1.0 by RedEnchilada and Pac, slightly modified as needed)
//player.mo.currentaxis is only *not* 0 if the player is in Axis2D mode, so use
//that to check
-- Toggle for spinning spring animation (for giggles)
local springspin = CV_RegisterVar({"springspin", "Off", 0, CV_OnOff})
-- Axes found, so we don't have to look them up later
local axes = false
addHook("MapChange", do axes = false end)
addHook("LinedefExecute", function(l, pmo) -- Switched "player" to "pmo" to be slightly more descriptive
-- This grabs another linedef from the tag
-- on the Call Lua Function effect
-- can be used for settings, etc
if not axes then
axes = {}
--print("Preparing Axis2D cache...")
for mo in thinkers.iterate("mobj") do
if mo.type == MT_AXIS then
--print("Axis found!")
local axisinfo = {}
axisinfo.x = mo.x
axisinfo.y = mo.y
axisinfo.radius = mo.spawnpoint.angle
axisinfo.flipped = false
if axisinfo.radius >= 16384 then
axisinfo.radius = $1-16384
axisinfo.flipped = true
end
axes[mo.spawnpoint.options] = axisinfo
--print("Storing axis #" .. mo.spawnpoint.options .. " in table...")
--print(axisinfo.x .. " " .. axisinfo.y .. " " .. axisinfo.radius)
elseif mo.type == MT_AXISTRANSFERLINE then
--print("Line axis found!")
local axisinfo = {}
axisinfo.basex = mo.x
axisinfo.basey = mo.y
axisinfo.angle = mo.angle
axes[mo.spawnpoint.options] = axisinfo
--print("Storing axis #" .. mo.spawnpoint.options .. " in table...")
--print(axisinfo.basex .. " " .. axisinfo.basey .. " " .. axisinfo.angle)
elseif mo.type == MT_AXISTRANSFER then
continue -- Ignore these, but keep going in the list
else
--print("End of list.")
break -- Axis objects always start off the list, so now we know there are no more to parse
end
end
end
pmo.glidediff = nil
if l.tag == 0 then
pmo.currentaxis = nil
--print("Ejecting the player from the 2D track...")
local player = pmo.player
if player and player.movevars then
player.normalspeed = player.movevars.normalspeed
player.thrustfactor = player.movevars.thrustfactor
player.accelstart = player.movevars.accelstart
player.acceleration = player.movevars.acceleration
player.movevars = nil
if player.charability == CA_THOK then
player.actionspd = 2*$1
end
player.runspeed = (3*$1)/2
player.jumpfactor = 10*$1/11
end
return
end
local axis = axes[l.tag]
if not axis then
print("ERROR: Axis " .. l.tag .. " does not exist! Please create it!")
return
end
-- Get extra properties from linedef
local linegrab = P_FindSpecialLineFromTag(9000, l.tag, -1)
if linegrab ~= -1 then
linegrab = lines[linegrab]
else
linegrab = nil
end
if linegrab then
axis.camangle = R_PointToAngle2(0, 0, linegrab.dx, linegrab.dy)
if linegrab.flags & ML_NOCLIMB then
axis.camangleabs = true
else
axis.camangleabs = false
end
if linegrab.flags & ML_EFFECT1 then
axis.camdist = R_PointToDist2(0, 0, linegrab.dx, linegrab.dy)
else
axis.camdist = false
end
end
pmo.currentaxis = axis
--print("Changing to axis " .. l.tag)
--if axis.angle then
-- print("Axis is a straight line.")
--end
end, "P_DoAngleSpin")
-- Snap mobj to axis
local function Ax_SnapMobj(mo)
if not mo.currentaxis then return end -- Safety precaution!
local angle
-- Straight line axes
if mo.currentaxis.angle ~= nil then
angle = mo.currentaxis.angle-ANGLE_90
mo.currentaxis.x = mo.x+cos(angle)
mo.currentaxis.y = mo.y+sin(angle)
mo.currentaxis.radius = 1
mo.currentaxis.flipped = false
else -- Circular axes
angle = R_PointToAngle2(mo.currentaxis.x, mo.currentaxis.y, mo.x, mo.y)
end
-- Snap player to position on axis
local snapx, snapy
if mo.currentaxis.angle ~= nil then
local pangle = R_PointToAngle2(mo.currentaxis.basex, mo.currentaxis.basey, mo.x, mo.y)
pangle = $1-mo.currentaxis.angle
if pangle >= FRACUNIT or pangle <= -FRACUNIT then
local pdist = R_PointToDist2(mo.currentaxis.basex, mo.currentaxis.basey, mo.x, mo.y)
pdist = FixedMul(cos(pangle), pdist)
snapx = mo.currentaxis.basex + FixedMul(pdist, cos(mo.currentaxis.angle))
snapy = mo.currentaxis.basey + FixedMul(pdist, sin(mo.currentaxis.angle))
else
mo.oldpos = {
x = mo.x,
y = mo.y--,
--z = mo.z
}
return -- Close enough, let's just not worry about moving them around
end
else
snapx = mo.currentaxis.x+(cos(angle)*mo.currentaxis.radius)
snapy = mo.currentaxis.y+(sin(angle)*mo.currentaxis.radius)
end
if mo.oldpos and not P_TryMove(mo, snapx, snapy, true) then
-- There was an issue adjusting the player to the axis. Figure this part out later!
P_TeleportMove(mo, mo.oldpos.x, mo.oldpos.y, mo--[[.oldpos]].z)
--mo.momx = $1/-5
--mo.momy = $1/-5
--print("HIT")
end
mo.oldpos = {
x = mo.x,
y = mo.y--,
--z = mo.z
}
if mo.player then return end -- The player mobj handles this already!
-- Normalize momentum to angle
local newmag = R_PointToDist2(0, 0, mo.momx, mo.momy)
local oldmag = R_PointToAngle2(0, 0, mo.momx, mo.momy)-angle
if oldmag > 0 then
oldmag = ANGLE_90
else
oldmag = -ANGLE_90
end
P_InstaThrust(mo, angle+oldmag, newmag)
end
-- Player management!
addHook("MobjThinker", function(mo)
local player = mo.player
if mo.currentaxis then
--[[ Parse turn keys (commenting out because it causes unfixable issues, if anyone can fix this that'd be great)
if mo.prevangle == nil then
mo.prevangle = mo.angle
end
local turn = mo.prevangle-mo.angle
if turn < ANGLE_135 and turn > ANGLE_225 then
turn = $1/360000
print(("%s %s %s"):format(turn, mo.angle, mo.prevangle))
mo.angle = mo.prevangle
player.cmd.sidemove = ((function(v)
v = v+turn
if v>50 then
return 50
elseif v<-50
return -50
else
return v
end
end)($1))
end]]
if not player.climbing then
if player.cmd.sidemove < 0 then
mo.isfacingleft = true
elseif player.cmd.sidemove > 0 then
mo.isfacingleft = false
end
if player.onwall and (player.cmd.buttons & BT_JUMP) and not (player.onwall & BT_JUMP) then
mo.isfacingleft = not mo.isfacingleft
end
player.onwall = false
else
player.onwall = 1|(player.cmd.buttons & BT_JUMP)
-- Remove all horizontal momentum to prevent player from falling off walls when pushing horizontal input as climbing starts
mo.momx = 0
mo.momy = 0
end
local angle
-- Straight line axes
if mo.currentaxis.angle ~= nil then
angle = mo.currentaxis.angle-ANGLE_90
mo.currentaxis.x = mo.x+cos(angle)
mo.currentaxis.y = mo.y+sin(angle)
mo.currentaxis.radius = 1
mo.currentaxis.flipped = false
else -- Circular axes
angle = R_PointToAngle2(mo.currentaxis.x, mo.currentaxis.y, mo.x, mo.y)
end
-- Snap player to position on axis
Ax_SnapMobj(mo)
-- Handle camera
if player.health then -- Don't move the camera when the player's dead!
--local factor = 1
local camangle = angle
if mo.currentaxis.flipped then
--factor = -1
camangle = $1+ANGLE_180
end
if mo.currentaxis.camangle ~= nil then
if mo.currentaxis.camangleabs then
camangle = 0
end
camangle = $1+mo.currentaxis.camangle
end
if not mo.currentaxis.camdist then
mo.currentaxis.camdist = 448*FRACUNIT
end
if not player.awayviewmobj then
player.awayviewmobj = P_SpawnMobj(mo.x, mo.y, mo.z, MT_THOK)
P_TeleportMove(player.awayviewmobj, mo.currentaxis.x+(cos(angle)*mo.currentaxis.radius)+FixedMul(cos(camangle), mo.currentaxis.camdist), mo.currentaxis.y+(sin(angle)*mo.currentaxis.radius)+FixedMul(sin(camangle), mo.currentaxis.camdist), mo.z+20*FRACUNIT)
end
P_TeleportMove(player.awayviewmobj, player.awayviewmobj.x+((mo.currentaxis.x+(cos(angle)*mo.currentaxis.radius)+FixedMul(cos(camangle), mo.currentaxis.camdist))-player.awayviewmobj.x)/4, player.awayviewmobj.y+((mo.currentaxis.y+(sin(angle)*mo.currentaxis.radius)+FixedMul(sin(camangle), mo.currentaxis.camdist))-player.awayviewmobj.y)/4, player.awayviewmobj.z+(mo.z+10*FRACUNIT-player.awayviewmobj.z)--[[/4]])
end
player.awayviewtics = 2
player.awayviewmobj.angle = R_PointToAngle2(player.awayviewmobj.x, player.awayviewmobj.y, mo.x, mo.y)
-- Set player angle
if(player.pflags & PF_GLIDING) then
local tangle = angle
if mo.currentaxis.flipped then
tangle = $1^^ANGLE_180
end
if not mo.glidediff then
mo.glidediff = mo.angle-tangle
end
if abs(player.cmd.sidemove) < 3 then -- Default angle to what it's at now
mo.isfacingleft = mo.glidediff < 0
end
if mo.isfacingleft then
mo.glidediff = $1-(ANG10/2)
if mo.glidediff < ANGLE_270 then
mo.glidediff = ANGLE_270
end
else
mo.glidediff = $1+(ANG10/2)
if mo.glidediff > ANGLE_90 then
mo.glidediff = ANGLE_90
end
end
mo.angle = tangle+mo.glidediff
-- Fuck this game's shitty latching-on code! I'm rewriting it myself!
if not player.skidtime then
P_InstaThrust(mo, mo.angle, FixedMul(FixedMul(player.actionspd + player.glidetime*1500, mo.scale), abs(sin(mo.angle-angle))))
if P_TryMove(mo, mo.x+mo.momx, mo.y+mo.momy, true) then -- Check if this will send the player into a wall
P_TeleportMove(mo, mo.x-mo.momx, mo.y-mo.momy, mo.z) -- Now put them back for reasons.
elseif not player.lastglideattempt or abs(player.lastglideattempt.x-mo.x)+abs(player.lastglideattempt.y-mo.y) > FRACUNIT then -- Don't check if we've already checked, for optimization reasons
player.lastglideattempt = {
x = mo.x,
y = mo.y
}
--print("Climb, damn you!")
-- Move player as close as we can to the wall
mo.momx = $1/32
mo.momy = $1/32
local moves = 31
while P_TryMove(mo, mo.x+mo.momx, mo.y+mo.momy, true) and moves do moves = $1-1 end
if moves then
-- Look for climbable wall
local line, dist, x, y = nil, 40<<FRACBITS, 0, 0
for l in lines.iterate do -- SSSLLLOOOWWW look for a method to only get lines from the active sector
if l.frontsector == l.backsector then continue end -- Just a decoration linedef, ignore...
if l.frontsector ~= mo.subsector.sector and l.backsector ~= mo.subsector.sector then continue end -- Line isn't in our sector!
local xtest, ytest = P_ClosestPointOnLine(mo.x, mo.y, l)
if (xtest < l.v1.x-20 and xtest < l.v2.x-20)
or (xtest > l.v1.x+20 and xtest > l.v2.x+20)
or (ytest < l.v1.y-20 and ytest < l.v2.y-20)
or (ytest > l.v1.y+20 and ytest > l.v2.y+20) then
continue -- Closest point is outside of the line!
end
local dangle = R_PointToAngle2(mo.x, mo.y, xtest, ytest)-mo.angle
if dangle > ANGLE_90 or dangle < ANGLE_270 then
continue -- Closest point is not in front of us!
end
local newdist = P_AproxDistance(abs(mo.x-xtest), abs(mo.y-ytest))
if newdist > 12*FRACUNIT and newdist < dist then
dist = newdist
x = xtest
y = ytest
line = l
end
end
--print(("#%s %su away at %s,%s (angle: %s)"):format(#line, dist/FRACUNIT, x/FRACUNIT, y/FRACUNIT, AngleFixed(abs(R_PointToAngle2(mo.x, mo.y, x, y)-mo.angle))/FRACUNIT))
if line and not (line.flags & ML_NOCLIMB) then
S_StartSound(player.mo, sfx_s3k4a)
P_ResetPlayer(player)
player.lastlinehit = #line
player.climbing = 5
mo.momx, mo.momy, mo.momz = 0, 0, 0
else
player.climbing = 0
mo.momx, mo.momy = 0, 0
end
end
end
else
P_InstaThrust(mo, mo.angle, FixedMul(FixedMul(player.actionspd - player.glidetime*FRACUNIT, mo.scale), abs(sin(mo.angle-angle))))
end
elseif mo.state == S_PLAY_SPRING and springspin.value then
if mo.isfacingleft then
mo.angle = angle-ANG20*leveltime
else
mo.angle = angle+ANG20*leveltime
end
else
mo.glidediff = 0
if player.climbing then -- Actually, set isfacingleft based on the climbing angle!
if mo.currentaxis.flipped then
mo.isfacingleft = (angle-mo.angle) < 0
else
mo.isfacingleft = (angle-mo.angle) > 0
end
end
if mo.isfacingleft then
mo.angle = angle-ANGLE_90
else
mo.angle = angle+ANGLE_90
end
if mo.currentaxis.flipped then
mo.angle = $1+ANGLE_180
end
end
-- Rip out normal movement and do it ourselves! Muahaha!
player.movevars = {
normalspeed = skins[mo.skin].normalspeed,
thrustfactor = skins[mo.skin].thrustfactor,
accelstart = skins[mo.skin].accelstart,
acceleration = skins[mo.skin].acceleration
}
player.normalspeed = 0
player.thrustfactor = 0
player.accelstart = 0
player.acceleration = 0
player.runspeed = 2*skins[mo.skin].runspeed/3
player.jumpfactor = 11*skins[mo.skin].jumpfactor/10
if player.charability == CA_THOK then
player.actionspd = skins[mo.skin].actionspd/2
end
-- Normalize momentum to angle
local newmag = R_PointToDist2(0, 0, mo.momx, mo.momy)
local oldmag = R_PointToAngle2(0, 0, mo.momx, mo.momy)-angle
if oldmag > 0 then
oldmag = ANGLE_90
else
oldmag = -ANGLE_90
end
P_InstaThrust(mo, angle+oldmag, newmag)
-- Referencing player movement code and kinda recreating it here
if not player.climbing and not (player.pflags & PF_GLIDING)
and not player.exiting and not (player.pflags & PF_STASIS)
and not P_PlayerInPain(player) and player.health then
local m = player.movevars
local topspeed = (2*m.normalspeed)/3
local thrustfactor = m.thrustfactor
local acceleration = m.accelstart + (FixedDiv(player.speed, mo.scale)/FRACUNIT) * m.acceleration
if player.powers[pw_tailsfly] then
topspeed = $1/2
thrustfactor = $1*2
elseif mo.eflags & (MFE_UNDERWATER|MFE_GOOWATER) then
topspeed = $1/2
acceleration = 2*$1/3
end
if player.powers[pw_super] or player.powers[pw_sneakers] then
thrustfactor = $1*2
acceleration = $1/2
topspeed = $1*2
end
local movepushside = player.cmd.sidemove*thrustfactor*acceleration
if not P_IsObjectOnGround(mo) then
movepushside = $1/2
if player.powers[pw_tailsfly] and player.speed > topspeed then
player.speed = topspeed-1
movepushside = $1/4
end
end
if player.pflags & PF_SPINNING then
if not (player.pflags & PF_STARTDASH) then
movepushside = $1/48
else
movepushside = 0
end
end
movepushside = FixedMul(movepushside, mo.scale)
local oldmag = R_PointToDist2(0, 0, mo.momx, mo.momy)
if mo.currentaxis.flipped then
P_Thrust(mo, angle-ANGLE_90, movepushside)
else
P_Thrust(mo, angle+ANGLE_90, movepushside)
end
local newmag = R_PointToDist2(0, 0, mo.momx, mo.momy)
if newmag > topspeed then
if oldmag > topspeed then
if newmag > oldmag then
mo.momx = FixedMul(FixedDiv($1, newmag), oldmag)
mo.momy = FixedMul(FixedDiv($1, newmag), oldmag)
end
else
mo.momx = FixedMul(FixedDiv($1, newmag), topspeed)
mo.momy = FixedMul(FixedDiv($1, newmag), topspeed)
end
end
end
--[[ Follow-up stuff (player animations and such) for using turn buttons to move
mo.prevangle = mo.angle
if ({[PA_IDLE] = true, [PA_WALK] = true, [PA_RUN] = true})[player.panim] then
--print(player.speed)
--print(player.runspeed)
if player.speed >= player.runspeed then
if player.panim ~= PA_RUN then
player.panim = PA_RUN
mo.state = S_PLAY_SPD1
end
elseif player.speed > FRACUNIT/2 then
if player.panim ~= PA_WALK then
player.panim = PA_WALK
mo.state = S_PLAY_RUN1
end
else
player.panim = PA_IDLE
end
end]]
elseif player.movevars then
player.normalspeed = skins[mo.skin].normalspeed
player.thrustfactor = skins[mo.skin].thrustfactor
player.accelstart = skins[mo.skin].accelstart
player.acceleration = skins[mo.skin].acceleration
player.movevars = nil
if player.charability == CA_THOK then
player.actionspd = skins[mo.skin].actionspd
end
player.runspeed = skins[mo.skin].runspeed
player.jumpfactor = skins[mo.skin].jumpfactor
end
end, MT_PLAYER)
-- Get linedef executors that trigger axis changers
-- Table of sector numbers
local axisChangeExecs = {}
-- Function to reset
local function resetAxisChangeExecTable()
-- Get all of LD443 that change axes, and all of LD300
local ld443 = {}
local ld300 = {}
for line in lines.iterate do
if line.special == 300 then
table.insert(ld300, line)
elseif line.special == 443 and line.frontside.text == "P_DOANGLESPIN" then
table.insert(ld443, line)
end
end
-- Reset axis changer directory
axisChangeExecs = {}
-- Compare frontsectors of linedefs to find matches
for _,trigger in pairs(ld300) do
for _,func in pairs(ld443) do
if trigger.frontsector == func.frontsector then
table.insert(axisChangeExecs, trigger.tag) -- We only need the tag
--print("!")
break
end
end
end
-- Done!
end
-- Hooks
addHook("MapLoad", resetAxisChangeExecTable)
addHook("MapChange", resetAxisChangeExecTable)
-- LIBRARY red.func.mobjsectoriterate - get all sectors an object is "in" (including FOFs) and execute a function on them
-- (I realize P_PlayerTouchingSectorSpecial exists, but this works with arbitrary objects too)
-- @author RedEnchilada
-- @param mo - object to test
-- @param func - function(mo, sector) to execute
local function P_IterateMobjSectors(mo, func)
local sec = mo.subsector.sector
func(mo, sec)
local tag = sec.tag
local foftypes = { -- Linedef types of all FOF specials (MODIFIED FROM ORIGINAL FUNC for performance reasons, for axis checking - assume axes only switch on 223!)
--100, 101, 102, 103, 104, 105, 140, 141, 142, 143, 144, 145, 146, -- FOF (solid)
--[[120, 121, 122, 123, 124, 125, 220, 221, 222, ]]223--, -- FOF (intangible)
--170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, -- FOF (crumbling)
--150, 151, 152, 160, 190, 191, 192, 193, 194, 195, -- FOF (bobbing)
--200, 201, 202, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, -- FOF (special)
} -- Referenced from the SRB2DB 2.1 config
for i = 1, #foftypes do
local foftype = foftypes[i]
local linedefnum = P_FindSpecialLineFromTag(foftype, tag, -1)
local last = -1
while linedefnum ~= last do
local fof = lines[linedefnum]
if not fof then continue end
fof = fof.frontsector
if mo.z <= fof.ceilingheight and mo.z+mo.height >= fof.floorheight then
func(mo, fof)
end
--last = linedefnum
linedefnum = P_FindSpecialLineFromTag(foftype, tag, linedefnum)
end
end
end
-- /LIBRARY red.func.mobjsectoriterate
-- Function to place an object along axes
local function Ax_HandleSwitches(mo)
P_IterateMobjSectors(mo, function(mo, sector)
if ((sector.special >> 4) & 15) == 4 then -- Trigger Linedef Executor (Anywhere in Sector)
for _,i in ipairs(axisChangeExecs) do
if sector.tag == i then
P_LinedefExecute(i, mo, sector) -- Trigger linedef exec
return
end
end
end
end)
end
addHook("MobjThinker", function(mo)
-- Set lost rings to the player's axis on spawn
-- (Do this in MobjThinker instead of MobjSpawn because object mom hasn't initialized by the
-- time MobjSpawn hook is called!)
if not mo.spawnchecked then
mo.spawnchecked = true
if mo.target and mo.target.currentaxis then
mo.currentaxis = mo.target.currentaxis
-- Set horizontal momentum based on player's angle
P_InstaThrust(mo, mo.target.angle, mo.momx)
end
end
-- Snap rings to axes
if not (mo.fuse & 1) then -- Make it not run every frame for performance's sake
Ax_SnapMobj(mo)
end
if not (mo.fuse & 7) then
Ax_HandleSwitches(mo)
end
end, MT_FLINGRING)
//Extra character actions
addHook("ThinkFrame", do
for player in players.iterate
if (player.mo and player.mo.health) then
//Define false-invincibility variable
if player.mo.tempinvinc == nil then
player.mo.tempinvinc = 0
end
//False invincibility runs out over time
if player.mo.tempinvinc > 0 then
player.mo.tempinvinc = $1 - 1
end
//Define variable that determines whether to spawn an invincibility sparkle
if player.mo.spawnsparkle == nil then
player.mo.spawnsparkle = 4
end
//Spawn sparkles if false invincibility is present
if not (player.mo.tempinvinc == 0)
and not (player.powers[pw_invulnerability] != 0)
and not (player.powers[pw_super] != 0) then
//Spawn sparkle if variable is correct
if player.mo.spawnsparkle == 4 then
local th = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_IVSP)
th.scale = player.mo.scale
if player.mo.eflags & MFE_VERTICALFLIP then
th.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
th.z = $1 - (th.height/4)
end
end
player.mo.spawnsparkle = ($1+1) % 5
else
player.mo.spawnsparkle = 4
end
//Define spin-but-not-spin-pressed variable
if player.spinbutnotpress == nil then
player.spinbutnotpress = false
end
//Characters can uncurl while spinning
if (player.cmd.buttons & BT_USE)
and (player.pflags & PF_USEDOWN)
and (P_PlayerActionCheck(player) == true)
and (player.pflags & PF_SPINNING)
and not (player.pflags & PF_STARTDASH)
and not (player.pflags & PF_JUMPED)
and (player.spinbutnotpress == true) then
//Player is no longer spinning
player.pflags = $1 & ~PF_SPINNING
//If on the ground, switch to running; otherwise, switch to falling
if P_IsObjectOnGround(player.mo) then
player.mo.state = S_PLAY_SPD1
else
player.mo.state = S_PLAY_FALL1
end
end
//Determine spin-but-not-spin-pressed variable
if (player.pflags & PF_SPINNING)
and not (player.cmd.buttons & BT_USE)
and not (player.pflags & PF_USEDOWN) then
player.spinbutnotpress = true
else
player.spinbutnotpress = false
end
//Characters switch to falling animation after running off of a ledge
if (player.pflags == player.pflags & ~(PF_JUMPED|PF_SPINNING))
and not (player.mo.state == S_PLAY_PAIN)
and not (P_IsObjectOnGround(player.mo))
and ((player.mo.state == S_PLAY_STND)
or (player.mo.state == S_PLAY_TAP1)
or (player.mo.state == S_PLAY_TAP2)
or (player.panim == PA_WALK)
or (player.panim == PA_RUN)) then
player.mo.state = S_PLAY_FALL1
end
//Define was-just-super variable
if player.wasjustsuper == nil then
player.wasjustsuper = false
end
//Characters can attract rings while super
if player.powers[pw_super] != 0 then
player.powers[pw_shield] = SH_ATTRACT
end
//If player was just super and has an attraction shield, remove it
if (player.powers[pw_super] == 0)
and (player.wasjustsuper == true) then
player.powers[pw_shield] = SH_NONE
end
//Sonic's actions
if (player.mo.skin == "sonic") then
//Give Sonic a Homing Crush
player.charability = CA_HOMINGTHOK
player.actionspd = 108*FRACUNIT
player.thokitem = MT_HOMINGCRUSH
//Homing Crush secondary sound effect
if (player.homing)
and not (player.hominglasttic)
and (P_PlayerActionCheck(player) == true) then
S_StartSound(player.mo, sfx_hmgthk)
end
//Sonic has false invincibility while in a Homing Crush
if (P_PlayerActionCheck(player) == true)
and (player.homing) then
player.mo.tempinvinc = 2
end
//Spindash modification
player.mindash = 30*FRACUNIT
player.maxdash = 90*FRACUNIT
//Sonic's Speed Thok
if (player.cmd.buttons & BT_USE)
and not (player.spinlasttic)
and (P_SpinActionCheck(player) == true)
and not (player.powers[pw_super] != 0) //Thok is replaced with float while super
and (player.pflags & PF_JUMPED)
and not (player.pflags & PF_THOKKED) then
//Play sound effects
S_StartSound(player.mo, sfx_zoom)
S_StartSound(player.mo, sfx_thok)
//Player has thokked and is in jumping frames
player.pflags = ($1|PF_JUMPED|PF_THOKKED) & ~PF_SPINNING
//Spawn thok object
P_PlayerThokTrail(player)
//Thrust player forward
local an = player.mo.angle
local speed = 54 //This is in fracunits
player.mo.momx = FixedMul(speed*cos(an),player.mo.scale)
player.mo.momy = FixedMul(speed*sin(an),player.mo.scale)
end
/* Commented out for now
//Initialize multi-jump variables
player.jumpsnum = 3
player.delaytime = 5
player.mustberunning = true
//Sonic's normal jumpfactor
player.defaultfactor = FRACUNIT
player.increasefactor = FRACUNIT/4
//Changing variables
if (player.currentjump == nil) then
player.currentjump = 0
end
if (player.multijumptimer == nil) then
player.multijumptimer = 0
end
if (player.wasjustjumping == nil) then
player.wasjustjumping = false
end
//Has the player just touched the floor?
if (P_PlayerActionCheck(player) == true)
and (player.mo.eflags & MFE_JUSTHITFLOOR)
and (player.wasjustjumping == true) then
if (player.mustberunning == false)
or (player.panim == PA_RUN) then
//Set the multi-jump timer
player.multijumptimer = player.delaytime + 1
if player.currentjump < (player.jumpsnum - 1) then
player.currentjump = $1 + 1
else
player.currentjump = (player.jumpsnum - 1)
end
end
end
//Decrease multi-jump timer over time except when jumping
if not (player.pflags & PF_JUMPED)
and (player.multijumptimer > 0) then
player.multijumptimer = $1 - 1
end
//Using Homing Crush or Speed Thok nullifies multijump
if ((player.homing) or (player.pflags & PF_THOKKED))
and (player.multijumptimer > 0) then
player.multijumptimer = 0
end
//Change jump factor and spawn ghost mobj if multijump is active
if (player.multijumptimer > 0) then
if (player.pflags & PF_JUMPED) then
player.multijumptimer = player.delaytime
end
player.jumpfactor = player.defaultfactor + (player.increasefactor*player.currentjump)
P_SpawnGhostMobj(player.mo)
else
player.currentjump = 0
player.jumpfactor = player.defaultfactor
end
*/
end
//Tails' actions
if (player.mo.skin == "tails") then
//Tails can't spin
player.charability2 = CA2_NONE
player.actionspd = 0
//Tails can't fly for more than 5 seconds
if player.powers[pw_tailsfly] > 5*TICRATE then
player.powers[pw_tailsfly] = 5*TICRATE
end
//Rewritten fly behavior
if ((player.mo.state == S_PLAY_ABL1)
or (player.mo.state == S_PLAY_ABL2))
and (P_PlayerActionCheck(player) == true) then
if (player.mo.eflags & MFE_VERTICALFLIP) then
//Reverse gravity physics
player.mo.flags = $1 & ~MF_NOGRAVITY
if (player.cmd.buttons & BT_JUMP) then
if player.mo.momz > (-5*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = $1 - 1*player.mo.scale
end
if abs(-3*player.mo.scale - player.mo.momz) <= (1*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = -3*player.mo.scale
end
elseif (player.cmd.buttons & BT_USE) then
if player.mo.momz < (29*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = $1 + 1*player.mo.scale
end
if abs(15*player.mo.scale - player.mo.momz) <= (1*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = 15*player.mo.scale
end
end
else
//Normal gravity physics
player.mo.flags = $1 & ~MF_NOGRAVITY
if (player.cmd.buttons & BT_JUMP) then
if player.mo.momz < (5*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = $1 + 1*player.mo.scale
end
if abs(3*player.mo.scale - player.mo.momz) <= (1*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = 3*player.mo.scale
end
elseif (player.cmd.buttons & BT_USE) then
if player.mo.momz > (-29*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = $1 - 1*player.mo.scale
end
if abs(-15*player.mo.scale - player.mo.momz) <= (1*player.mo.scale)/2 then
player.mo.flags = $1|MF_NOGRAVITY
player.mo.momz = -15*player.mo.scale
end
end
end
else
player.mo.flags = $1 & ~MF_NOGRAVITY
end
//Missile shoot
if (player.cmd.buttons & BT_USE)
and not (player.spinlasttic)
and (P_SpinActionCheck(player) == true)
and not (player.pflags & PF_SPINNING)
and not ((player.mo.state >= S_PLAY_ABL1) and (player.mo.state <= S_PLAY_SPC4))
and (player.weapondelay == 0) then
//Determine angle and slope
local an = player.mo.angle
local slope = player.aiming
//Player can't move and fire at the same time
player.mo.momx = 0
player.mo.momy = 0
//Animation change
if P_IsObjectOnGround(player.mo) then
player.mo.state = S_PLAY_STND
elseif player.pflags & PF_JUMPED then
player.pflags = $1 & ~PF_JUMPED
player.mo.state = S_PLAY_FALL1
end
//Fire away! Spawn 1 fracunit ahead of the player
S_StartSound(player.mo, sfx_s3k47)
local th = P_SpawnMobj(player.mo.x + (1*cos(an)), player.mo.y + (1*sin(an)), player.mo.z + 2*player.mo.height/3, MT_ROCKET)
//Missile copies player's scale
th.scale = player.mo.scale
// missiles can't hurt their targets, note
th.target = player.mo
//Determine speed
local speed = FixedMul(th.info.speed, player.mo.scale)
if player.powers[pw_super] != 0 then
speed = 2*$1
end
if player.mo.eflags & MFE_VERTICALFLIP then
th.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
th.z = $1 - th.height - player.mo.height/3
end
th.angle = an
th.momx = FixedMul(speed, cos(an))
th.momy = FixedMul(speed, sin(an))
th.momx = FixedMul(th.momx, cos(slope))
th.momy = FixedMul(th.momy, cos(slope))
th.momz = FixedMul(speed, sin(slope))
if player.powers[pw_super] != 0 then
player.weapondelay = TICRATE/4
else
player.weapondelay = TICRATE/2
end
end
//Tails goes into his spinning animation when jumping
if (P_PlayerActionCheck(player) == true)
and (player.pflags & PF_JUMPED)
and not (player.panim == PA_ROLL) then
player.mo.state = S_PLAY_ATK1
end
end
//Knuckles' actions
if (player.mo.skin == "knuckles") then
//Knuckles can jump just as high as everyone else
player.jumpfactor = 1*FRACUNIT
//Spindash modification
player.mindash = 15*FRACUNIT
player.maxdash = 60*FRACUNIT
//Initialize climbing variables
if player.climbenergy == nil then
player.climbenergy = 4*TICRATE*FRACUNIT
end
if player.climbframe == nil then
player.climbframe = 3
end
if player.climbnexttimer == nil then
player.climbnexttimer = 0
end
if player.climbedthisjump == nil then
player.climbedthisjump = false
end
//Reset everything if player is not climbing
if not (player.climbing) then
player.climbenergy = 4*TICRATE*FRACUNIT
player.climbframe = 3
player.climbnexttimer = 0
end
//Reset variable that determines if the player has climbed this jump
if not (player.climbing)
and not (player.pflags & PF_JUMPED) then
player.climbedthisjump = false
end
//Knuckles' climbing
if (player.climbing) then
player.climbedthisjump = true
if (player.climbenergy > 0) then
//Player climbing animation variables
if player.climbnexttimer == 0 then
player.climbframe = ($1 + 1) % 4
if (player.climbenergy >= 3*TICRATE*FRACUNIT) then
player.climbnexttimer = 5
elseif (player.climbenergy >= 2*TICRATE*FRACUNIT) then
player.climbnexttimer = 4
elseif (player.climbenergy >= 1*TICRATE*FRACUNIT) then
player.climbnexttimer = 3
else
player.climbnexttimer = 2
end
end
if player.climbnexttimer > 0 then
player.climbnexttimer = $1 - 1
end
//Climbing animation state set
if (player.climbframe == 0) then
player.mo.state = S_PLAY_CLIMB2
elseif (player.climbframe == 1) then
player.mo.state = S_PLAY_CLIMB3
elseif (player.climbframe == 2) then
player.mo.state = S_PLAY_CLIMB4
elseif (player.climbframe == 3) then
player.mo.state = S_PLAY_CLIMB5
end
//Drain climb energy over time depending on player movement
player.climbenergy = $1 - 1*FRACUNIT
if (player.cmd.forwardmove) > 0 then
player.climbenergy = $1 - (1*FRACUNIT/2)
elseif (player.cmd.forwardmove < 0) then
player.climbenergy = $1 + (1*FRACUNIT/2)
end
if (player.cmd.sidemove != 0) then
player.climbenergy = $1 - (1*FRACUNIT/2)
end
else
//Drop off the wall if out of climb energy
player.climbing = 0
player.pflags = ($1|PF_THOKKED) & ~(PF_JUMPED|PF_SPINNING)
player.mo.state = S_PLAY_FALL1
end
end
//If jumping after climbing, exit out of jumping
if not (player.climbing)
and (player.climbedthisjump == true) then
player.climbedthisjump = false
if not (player.powers[pw_super] != 0) then
player.pflags = ($1|PF_THOKKED) & ~(PF_JUMPED|PF_SPINNING)
player.mo.state = S_PLAY_FALL1
end
end
//Initialize stomping variables
if player.stomping == nil then
player.stomping = false
end
if player.wasjuststomping == nil then
player.wasjuststomping = false
end
//Begin stomping if ready
if (player.cmd.buttons & BT_USE)
and not (player.spinlasttic)
and (P_SpinActionCheck(player) == true)
and (player.pflags & PF_JUMPED)
and not (player.pflags & PF_GLIDING)
and not (player.climbing)
and not (player.mo.eflags & MFE_GOOWATER)
and not (player.stomping == true) then
S_StartSound(player.mo, sfx_zoom)
player.stomping = true
end
//Stop stomping if no longer able
if (P_SpinActionCheck(player) == false)
or not (player.pflags & PF_JUMPED)
or (player.pflags & PF_GLIDING)
or (player.climbing)
or (player.mo.eflags & MFE_GOOWATER) then
player.stomping = false
end
//Stomping effects
if player.stomping == true then
player.mo.momx = 0
player.mo.momy = 0
player.mo.momz = FixedMul(-32*FRACUNIT, P_GravAndScale(player.mo))
P_SpawnGhostMobj(player.mo)
end
//Create stomp explosion if just stopped stomping
if (P_PlayerActionCheck(player) == true)
and (P_IsObjectOnGround(player.mo) == true)
and not (player.pflags & PF_JUMPED)
and not (player.pflags & PF_GLIDING)
and not (player.climbing)
and (player.stomping == false)
and (player.wasjuststomping == true) then
S_StartSound(player.mo, sfx_s3k98)
//Create dust objects
for i = 0,31
local offset = 1*FRACUNIT*P_MobjFlip(player.mo)
local dust = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z + offset, MT_STOMPDUST)
dust.target = player.mo
dust.scale = player.mo.scale
if player.mo.eflags & MFE_VERTICALFLIP then
dust.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
end
local an = i*ANGLE_11hh
local speed = FixedMul((dust.info.speed/FRACUNIT), player.mo.scale)
dust.momx = speed*cos(an)
dust.momy = speed*sin(an)
dust.momz = 0
end
end
//Determine was-just-stomping variable
player.wasjuststomping = player.stomping
end
//Metal Sonic's actions
if (player.mo.skin == "metal_sonic") then
//Infinite breath in water and space
player.powers[pw_underwater] = 1073741824
player.powers[pw_spacetime] = 1073741824
//Metal doesn't lose control when hit
if (player.mo.state == S_PLAY_PAIN)
and (P_PlayerInPain(player)) then
player.mo.state = S_PLAY_FALL1
end
//Metal goes into his spinning animation when jumping
if (P_PlayerActionCheck(player) == true)
and (player.pflags & PF_JUMPED)
and not (player.panim == PA_ROLL) then
player.mo.state = S_PLAY_ATK1
end
//Metal has false invincibility while in a spindash
if (P_PlayerActionCheck(player) == true)
and (player.pflags & PF_SPINNING)
and not (player.pflags & PF_JUMPED) then
player.mo.tempinvinc = 2
end
//Add thokked flag upon pressing Jump in mid-air
if (player.cmd.buttons & BT_JUMP)
and not (player.jumplasttic)
and (player.pflags & PF_JUMPED)
and (player.wasjustjumping)
and not (player.pflags & PF_THOKKED)
and (P_PlayerActionCheck(player) == true)
and (player.readylasttic) then
player.pflags = $1|PF_THOKKED
end
//Initialize charging state variable
if player.chargestate == nil then
player.chargestate = 0
end
//Initialize charge power variable
if player.chargepower == nil then
player.chargepower = 0
end
//Initialize charge flashing variable
if player.chargeflash == nil then
player.chargeflash = -1
end
//Reset charging if player is ready to perform another charge
if ((P_PlayerActionCheck(player) == false)
or (P_IsObjectOnGround(player.mo) == true)) then
player.chargestate = 0
player.chargepower = 0
player.chargeflash = -1
end
//Initiate air charging if player uses mid-air ability
if (player.cmd.buttons & BT_JUMP)
and (player.pflags & PF_JUMPED)
and (player.pflags & PF_THOKKED)
and (player.chargestate == 0)
and (P_PlayerActionCheck(player) == true) then
S_StartSound(player.mo, sfx_mschrg)
player.chargestate = 1
player.chargepower = 0
player.chargeflash = -1
end
//Stop air charging if player releases Jump
if not (player.cmd.buttons & BT_JUMP)
and (player.chargestate >= 1)
and (player.chargestate <= 3)
and (P_PlayerActionCheck(player) == true) then
//Initiate dash if player is in the right state
if (player.chargestate >= 1)
and (player.chargestate <= 2) then
S_StartSound(player.mo, sfx_zoom)
player.pflags = ($1|PF_SPINNING) & ~PF_JUMPED
player.mo.state = S_PLAY_ATK1
local an = player.mo.angle
for i = 0,6
if player.chargepower > 0 then
player.chargepower = $1 - 1
end
end
if player.chargepower > 28 then
player.chargepower = 28
end
local speed = 30*FRACUNIT + FixedMul(30*FRACUNIT, (player.chargepower*FRACUNIT)/28)
player.mo.momx = FixedMul(FixedMul(speed,cos(an)), player.mo.scale)
player.mo.momy = FixedMul(FixedMul(speed,sin(an)), player.mo.scale)
player.mo.momz = 0
else
player.mo.state = S_PLAY_FALL1
end
player.chargestate = 5
player.chargepower = 0
player.chargeflash = -1
end
//Stop air charging if player's after-gun animation is over
if (player.chargepower == 0)
and (player.chargestate == 4)
and (P_PlayerActionCheck(player) == true) then
player.chargestate = 5
player.chargepower = 0
player.chargeflash = -1
player.mo.state = S_PLAY_FALL1
end
//Stuff to do when air charging
if (player.chargestate >= 1)
and (player.chargestate <= 4)
and (P_PlayerActionCheck(player) == true) then
//No movement allowed, set normalspeed to 0
player.normalspeed = 0
player.mo.momx = 0
player.mo.momy = 0
player.mo.momz = 0
//Player is not jumping or spinning
player.pflags = $1 & ~(PF_JUMPED|PF_SPINNING)
//If enough power is built up, switch charging animation
if (player.chargepower >= 35)
and (player.chargestate == 1) then
player.chargepower = 35
player.chargestate = 2
player.chargeflash = -1
end
//If Spin is pressed with full dash charge, switch to gun charge
if (player.cmd.buttons & BT_USE)
and (player.chargestate == 2)
and (player.chargepower >= 35) then
player.chargestate = 3
player.chargepower = 0
end
/* Commented out for now
//If Spin is released while in gun charge, use gun attack
if not (player.cmd.buttons & BT_USE)
and (player.chargestate == 3) then
S_StartSound(player.mo, sfx_bexpld)
P_MetalKillEverything(player, 768*FRACUNIT)
player.chargestate = 4
player.chargepower = 18
end
*/
//Use gun attack when ready
if player.chargestate == 3 then
if player.powers[pw_super] == 0 then
S_StartSound(player.mo, sfx_bexpld)
P_MetalKillEverything(player, 768*FRACUNIT)
else
//If super, nuke everything
P_BlackOw(player)
player.powers[pw_shield] = SH_ATTRACT
end
player.chargestate = 4
player.chargepower = 18
end
//Stuff for 'scrunched-up' charge frames
if (player.chargestate >= 1)
and (player.chargestate <= 3) then
player.mo.tempinvinc = 2
player.mo.state = S_PLAY_FALL1
//Charge aura spawn
if not ((player.metalthok) and (player.metalthok.valid)) then
player.metalthok = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_PLAYMETALAURA)
else
P_TeleportMove(player.metalthok, player.mo.x, player.mo.y, player.mo.z)
end
player.metalthok.color = P_PlayerColorSpawn(player)
player.metalthok.target = player.mo
player.metalthok.scale = player.mo.scale
if player.mo.eflags & MFE_VERTICALFLIP then
player.metalthok.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
player.metalthok.z = $1 - (player.metalthok.height/4)
end
//Increase charge over time
if player.chargestate == 1 then
player.chargepower = $1 + 1
end
//If charge is high enough, flash
if player.chargestate == 2 then
player.chargepower = 35
player.chargeflash = -$1
if player.chargeflash == 1 then
player.mo.state = S_PLAY_METALCHARGE
end
end
if player.chargestate == 3 then
if player.chargepower > 0 then
player.chargepower = $1 - 1
else
//Function for targeting enemies goes here?
player.chargepower = 7
end
end
//Stuff for open after-gun charge state
else
player.mo.state = S_PLAY_SPRING
if player.chargepower > 0 then
player.chargepower = $1 - 1
end
//Charge aura removal
if (player.metalthok) and (player.metalthok.valid) then
P_RemoveMobj(player.metalthok)
end
end
else
//Reset normalspeed if Metal is not charging
player.normalspeed = skins[player.mo.skin].normalspeed
//Charge aura removal
if (player.metalthok) and (player.metalthok.valid) then
P_RemoveMobj(player.metalthok)
end
end
//Target ability
if (player.cmd.buttons & BT_TOSSFLAG)
and not (player.tossflaglasttic)
and (P_PlayerActionCheck(player) == true) then
//If no target, find one
if player.mo.orbtarget == nil then
player.mo.orbtarget = P_MetalTargetLook(player, 1536*FRACUNIT, false)
//If already has target, remove it
else
player.mo.orbtarget = nil
if (player.mo.orbret) and (player.mo.orbret.valid) then
P_RemoveMobj(player.mo.orbret)
player.mo.orbret = nil
player.aiming = 0
end
end
end
//If orb target no longer exists, set the variable to nil
if (player.mo.orbtarget)
and not (player.mo.orbtarget.valid) then
player.mo.orbtarget = nil
if (player.mo.orbret) and (player.mo.orbret.valid) then
P_RemoveMobj(player.mo.orbret)
player.mo.orbret = nil
player.aiming = 0
end
end
//If orb target no longer has health, set the variable to nil
if (player.mo.orbtarget) and (player.mo.orbtarget.valid) then
if not (player.mo.orbtarget.health > 0) then
player.mo.orbtarget = nil
if (player.mo.orbret) and (player.mo.orbret.valid) then
P_RemoveMobj(player.mo.orbret)
player.mo.orbret = nil
player.aiming = 0
end
end
end
//Stuff to do if there is currently a valid orb target
if (player.mo.orbtarget) and (player.mo.orbtarget.valid) then
//Add a target reticule if there is none
if not (player.mo.orbret) then
S_StartSound(player.mo, sfx_gbeep)
player.mo.orbret = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_CAPETARGET)
player.mo.orbret.color = P_PlayerColorSpawn(player)
player.mo.orbret.tracer = player.mo
player.mo.orbret.target = player.mo.orbtarget
//If there is one, set its color to the player's
else
player.mo.orbret.color = P_PlayerColorSpawn(player)
end
//Player faces target
player.mo.angle = R_PointToAngle2(player.mo.x, player.mo.y, player.mo.orbtarget.x, player.mo.orbtarget.y)
player.aiming = R_PointToAngle2(0, 0, FixedHypot(player.mo.orbtarget.x-player.mo.x, player.mo.orbtarget.y-player.mo.y), player.mo.orbtarget.z-player.mo.z)
end
//Orb shoot
if (player.cmd.buttons & BT_USE)
and not (player.spinlasttic)
and (P_SpinActionCheck(player) == true)
and not (player.pflags & PF_SPINNING)
and (player.weapondelay == 0)
and ((player.chargestate == 0) or (player.chargestate == 5)) then
//Determine angle and slope
local an = 0
local slope = 0
if (player.mo.orbtarget) and (player.mo.orbtarget.valid) then
an = R_PointToAngle2(player.mo.x, player.mo.y, player.mo.orbtarget.x, player.mo.orbtarget.y)
slope = R_PointToAngle2(0, 0, FixedHypot(player.mo.orbtarget.x-player.mo.x, player.mo.orbtarget.y-player.mo.y), player.mo.orbtarget.z-player.mo.z)
else
an = player.mo.angle
slope = player.aiming
end
//Player can't move and fire at the same time
player.mo.momx = 0
player.mo.momy = 0
//Animation change
if P_IsObjectOnGround(player.mo) then
player.mo.state = S_PLAY_STND
elseif player.pflags & PF_JUMPED then
player.pflags = $1 & ~PF_JUMPED
player.mo.state = S_PLAY_FALL1
end
//Fire away! Spawn 1 fracunit ahead of the player
S_StartSound(player.mo, sfx_s3k54)
local th = P_SpawnMobj(player.mo.x + (1*cos(an)), player.mo.y + (1*sin(an)), player.mo.z + player.mo.height/3, MT_PLAYMETALORB)
//Missile copies player's scale
th.scale = FixedDiv(player.mo.scale, 3*FRACUNIT)
// missiles can't hurt their targets, note
th.target = player.mo
//Missile will chase object that Metal is locked on to
if (player.mo.orbtarget) and (player.mo.orbtarget.valid) then
th.homingtarget = player.mo.orbtarget
else
th.homingtarget = nil
end
//Determine speed and whether shot was fired by a super player
local speed = FixedMul(th.info.speed, player.mo.scale)
if player.powers[pw_super] != 0 then
th.supershot = true
speed = 2*$1
else
th.supershot = nil
end
if player.mo.eflags & MFE_VERTICALFLIP then
th.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
th.z = $1 - th.height + player.mo.height/3
end
th.angle = an
th.momx = FixedMul(speed, cos(an))
th.momy = FixedMul(speed, sin(an))
th.momx = FixedMul(th.momx, cos(slope))
th.momy = FixedMul(th.momy, cos(slope))
th.momz = FixedMul(speed, sin(slope))
if player.powers[pw_super] != 0 then
player.weapondelay = TICRATE/4
else
player.weapondelay = TICRATE/2
end
end
end
//Jasper's actions
if (player.mo.skin == "jasper") then
//Jasper has false invincibility while in a spindash
if (P_PlayerActionCheck(player) == true)
and (player.pflags & PF_SPINNING)
and not (player.pflags & PF_JUMPED) then
player.mo.tempinvinc = 2
end
//Jasper's stop-type Spark Burst
if (player.cmd.buttons & BT_JUMP)
and not (player.jumplasttic)
and (player.pflags & PF_JUMPED)
and (player.wasjustjumping)
and not (player.pflags & PF_THOKKED)
and (P_PlayerActionCheck(player) == true)
and (player.readylasttic) then
//Play sound effect
S_StartSound(player.mo, sfx_bexpld)
//Player has thokked and is in jumping frames
player.pflags = ($1|PF_JUMPED|PF_THOKKED) & ~PF_SPINNING
//Spawn dummy Spark Burst object
local burst = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_DUMMY)
burst.target = player.mo
burst.scale = player.mo.scale
if player.mo.eflags & MFE_VERTICALFLIP then
burst.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
end
//Use dummy to perform Spark Burst actions, then remove it
burst.info.painchance = 320*FRACUNIT
A_CapeChase(burst, 16*FRACUNIT, 0)
A_SparkBurstThrust(player.mo, 0, 0)
A_MissileDestroy(burst, 320, 0)
A_RingExplode(burst, 0, 0)
P_RemoveMobj(burst)
end
//Jasper's fall-switch-type Spark Burst
if (player.cmd.buttons & BT_USE)
and not (player.spinlasttic)
and (P_SpinActionCheck(player) == true)
and (player.pflags & PF_JUMPED)
and not (player.pflags & PF_THOKKED) then
//Play sound effects
S_StartSound(player.mo, sfx_spndsh)
S_StartSound(player.mo, sfx_bexpld)
//Player has thokked and is in jumping frames
player.pflags = ($1|PF_JUMPED|PF_THOKKED) & ~PF_SPINNING
//Flip vertical momentum
player.mo.momz = $1 * -1
//Spawn thok object
P_PlayerThokTrail(player)
//Spawn dummy Spark Burst object
local burst = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_DUMMY)
burst.target = player.mo
burst.scale = player.mo.scale
if player.mo.eflags & MFE_VERTICALFLIP then
burst.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
end
//Use dummy to perform Spark Burst actions, then remove it
burst.info.painchance = 320*FRACUNIT
A_CapeChase(burst, 16*FRACUNIT, 0)
A_MissileDestroy(burst, 320, 0)
A_RingExplode(burst, 0, 0)
P_RemoveMobj(burst)
end
end
//From the perspective of next tic, was the player action-ready last tic?
if (P_PlayerActionCheck(player)) then
player.readylasttic = true
else
player.readylasttic = nil
end
//From the perspective of next tic, did the player press jump last tic?
if (player.cmd.buttons & BT_JUMP) then
player.jumplasttic = true
else
player.jumplasttic = nil
end
//From the perspective of next tic, did the player press spin last tic?
if (player.cmd.buttons & BT_USE) then
player.spinlasttic = true
else
player.spinlasttic = nil
end
//From the perspective of next tic, did the player press toss flag last tic?
if (player.cmd.buttons & BT_TOSSFLAG) then
player.tossflaglasttic = true
else
player.tossflaglasttic = nil
end
//From the perspective of next tic, did the player thok last tic?
if (player.pflags & PF_THOKKED) then
player.thokkedlasttic = true
else
player.thokkedlasttic = nil
end
//From the perspective of next tic, was the player jumping last tic?
if (player.pflags & PF_JUMPED) then
player.wasjustjumping = true
else
player.wasjustjumping = false
end
//From the perspective of next tic, was the player homing last tic?
if (player.homing) then
player.hominglasttic = true
else
player.hominglasttic = nil
end
//From the perspective of next tic, was the player super last tic?
if player.powers[pw_super] != 0 then
player.wasjustsuper = true
else
player.wasjustsuper = false
end
end
//If the player is dead, do some things
if (player.mo) and not (player.mo.health) then
//Metal Sonic's death actions
if (player.mo.skin == "metal_sonic") then
//Remove orb target/reticule
if (player.mo.orbtarget) then
player.mo.orbtarget = nil
if (player.mo.orbret) and (player.mo.orbret.valid) then
P_RemoveMobj(player.mo.orbret)
player.mo.orbret = nil
end
end
end
end
end
end)
//If player has false invincibility on, they cannot be damaged by non-instakill attacks
addHook("ShouldDamage", function(target, inflictor, source, damage)
if (damage < 10000) //Non-instakill
and (target.tempinvinc) then
if not (target.tempinvinc == 0) then
return false
end
end
end, MT_PLAYER)
//A_HomingCrushThrust (used to determine how the player should be thrusted after using a Homing Crush)
function A_HomingCrushThrust (actor, var1, var2)
//Is the player homing-attacking?
if (actor.player.homing) or (actor.player.hominglasttic) then
//If they have a valid target that's not a spring, reset their momentum
if (actor.target) and (actor.target.valid) then
if not (actor.target.flags & MF_SPRING) then
//Vertical thrusting prohibited while super
if actor.player.powers[pw_super] == 0 then
actor.momz = FixedMul(0*FRACUNIT, P_GravAndScale(actor))
end
end
else
//If no valid target, reset all momentum
actor.momx = 0
actor.momy = 0
//Vertical thrusting prohibited while super
if actor.player.powers[pw_super] == 0 then
actor.momz = FixedMul(0*FRACUNIT, P_GravAndScale(actor))
end
end
end
end
//A_SparkBurstThrust (used to determine how the player should be thrusted after using a Spark Burst)
function A_SparkBurstThrust (actor, var1, var2)
//Find the player whose corresponding mobj is the actor
local targetplayer = actor.player
if targetplayer != nil then
local play = targetplayer
//If the player is super, make the Spark Burst function as a double jump
if play.powers[pw_super] != 0 then
local an = actor.angle
actor.momx = FixedMul(24*cos(an), actor.scale)
actor.momy = FixedMul(24*sin(an), actor.scale)
actor.momz = FixedMul(12*FRACUNIT, P_GravAndScale(actor))
//If the player is not super, halt the actor's momentum
else
actor.momx = 0
actor.momy = 0
actor.momz = FixedMul(0*FRACUNIT, P_GravAndScale(actor))
end
end
end
//A_MetalOrbHoming (instructs Metal Sonic's orbs to follow their targets)
function A_MetalOrbHoming (actor, var1, var2)
//Make sure that actor's follow target is valid
if (actor.homingtarget) and (actor.homingtarget.valid) then
if (actor.homingtarget.health > 0) //Mobj is alive
and (actor.homingtarget.flags & MF_SHOOTABLE) then //Mobj is killable
local an = R_PointToAngle2(actor.x, actor.y, actor.homingtarget.x, actor.homingtarget.y)
local slope = R_PointToAngle2(0, 0, FixedHypot(actor.homingtarget.x-actor.x, actor.homingtarget.y-actor.y), actor.homingtarget.z-actor.z)
//Determine speed
local speed = FixedMul(actor.info.speed, (3*actor.scale))
if (actor.supershot) then
speed = 2*$1
end
//Change actor's momentum to face target
actor.angle = an
actor.momx = FixedMul(speed, cos(an))
actor.momy = FixedMul(speed, sin(an))
actor.momx = FixedMul(actor.momx, cos(slope))
actor.momy = FixedMul(actor.momy, cos(slope))
actor.momz = FixedMul(speed, sin(slope))
end
end
end
//A_PlayerTempInvinc (gives the target player a certain amount of false invincibility)
//VAR1: Tics of false invincibility to give (negative values are permanent)
function A_PlayerTempInvinc (actor, var1, var2)
//Does the actor have a valid target object?
if (actor.target) and (actor.target.valid) then
//Is the target a living player?
if (actor.target.type == MT_PLAYER)
and (actor.target.health) then
//Give them some false-invinc
actor.target.tempinvinc = var1
end
end
end
//A_CapeTargetThinker (thinker for capechasing target reticule)
//Target determines object to target
//Tracer determines 'master' object
function A_CapeTargetThinker (actor, var1, var2)
//Use a variable to determine if the actor has a tracer
actor.hastracer = false
if (actor.tracer)
and (actor.tracer.valid) then
actor.hastracer = true
end
//If the actor has a target...
if (actor.target)
and (actor.target.valid) then
//Turn visible
actor.flags2 = $1 & ~MF2_DONTDRAW
//Follow it
P_TeleportMove(actor, actor.target.x, actor.target.y, actor.target.z)
actor.momx = 0
actor.momy = 0
actor.momz = 0
//Copy its attributes
actor.scale = actor.target.scale
if actor.target.eflags & MFE_VERTICALFLIP then
actor.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
end
//If tracer present, point towards it
if actor.hastracer == true then
local an = R_PointToAngle2(actor.x, actor.y, actor.tracer.x, actor.tracer.y)
actor.angle = an
P_TeleportMove(actor, actor.x + 1*cos(an), actor.y + 1*sin(an), actor.z)
end
else
//If no target, turn invisible
actor.flags2 = $1|MF2_DONTDRAW
//Follow tracer and copy its attributes if there is one
if actor.hastracer == true then
P_TeleportMove(actor, actor.tracer.x, actor.tracer.y, actor.tracer.z)
actor.momx = 0
actor.momy = 0
actor.momz = 0
actor.scale = actor.tracer.scale
if actor.tracer.eflags & MFE_VERTICALFLIP then
actor.flags2 = $1|MF2_OBJECTFLIP // flip gravity!
end
end
end
end
//A_MissileDestroy (destroys all missile objects that are sufficiently close to the actor)
//VAR1: Maximum distance of destroyed projectiles, in fracunits
//VAR2: Height offset of center point, in fracunits
function A_MissileDestroy (actor, var1, var2)
//Determine center position
local xcen = actor.x
local ycen = actor.y
local zcen = actor.z + FixedMul(var2*FRACUNIT, P_GravAndScale(actor))
//Search for missile objects
for proj in thinkers.iterate("mobj")
//Is it a missile that is moving?
if (proj.flags & MF_MISSILE)
and not (proj.momx == 0 and proj.momy == 0 and proj.momz == 0) then
//Is it sufficiently close to the actor?
local projdist = FixedHypot(proj.z-zcen, FixedHypot(proj.x-xcen, proj.y-ycen))
if projdist <= FixedMul(var1*FRACUNIT, actor.scale) then
//Kill it
P_ExplodeMissile(proj)
end
end
end
end
//GDZ1-specific Lua code
addHook("MobjThinker", function(mo)
for player in players.iterate
if (player.mo and player.mo.health)
//If in the opening tunnel, make the player run towards the exit
if (((#player.mo.subsector.sector >= 1495) and (#player.mo.subsector.sector <= 1499))
or ((#player.mo.subsector.sector >= 1505) and (#player.mo.subsector.sector <= 1507)))
and (player.mo.z <= 256*FRACUNIT)
and (multiplayer == false) then
player.pflags = $1 & ~(PF_JUMPED|PF_SPINNING)
player.powers[pw_nocontrol] = 2
player.mo.angle = 0
A_Thrust(player.mo, (player.normalspeed/FRACUNIT), 1)
if player.panim != PA_RUN then
player.mo.state = S_PLAY_SPD1
end
if not (player.mo.initialrun) then
P_TeleportMove(player.mo, player.mo.x, player.mo.y, player.mo.z+1*FRACUNIT)
player.mo.momz = -60*FRACUNIT
player.mo.initialrun = true
end
else
player.mo.initialrun = nil
end
//Should the player be repositioned?
if (#player.mo.subsector.sector >= 950)
and (#player.mo.subsector.sector <= 952)
and (player.mo.flags2 == player.mo.flags2 & ~MF2_TWOD)
and not (player.mo.reposition) then
player.mo.reposition = true
end
//Reposition player for entrance into 2D section
if (player.mo.reposition) then
player.mo.flags = $1|MF_NOGRAVITY|MF_NOBLOCKMAP
player.pflags = ($1|PF_JUMPED) & ~PF_SPINNING
player.mo.angle = ANGLE_90
player.powers[pw_nocontrol] = 32767
player.mo.momx = 0
player.mo.momy = 0
player.mo.momz = 0
//Player should be in spin frames
if not (player.panim == PA_ROLL) then
S_StartSound(player.mo, sfx_spin)
player.mo.state = S_PLAY_ATK1
end
//Set x position
if (player.mo.x < 11840*FRACUNIT) then
P_TeleportMove(player.mo, player.mo.x+16*FRACUNIT, player.mo.y, player.mo.z)
elseif (player.mo.x > 14624*FRACUNIT) then
P_TeleportMove(player.mo, player.mo.x-16*FRACUNIT, player.mo.y, player.mo.z)
end
//Set y position
if (player.mo.y < 2144*FRACUNIT) then
P_TeleportMove(player.mo, player.mo.x, player.mo.y+16*FRACUNIT, player.mo.z)
elseif (player.mo.y > 2144*FRACUNIT) then
P_TeleportMove(player.mo, player.mo.x, player.mo.y-16*FRACUNIT, player.mo.z)
end
if (player.mo.y < (2144+9)*FRACUNIT)
and (player.mo.y > (2144-9)*FRACUNIT) then
P_TeleportMove(player.mo, player.mo.x, 2144*FRACUNIT, player.mo.z)
end
//Set z position
if (player.mo.z < 3072*FRACUNIT) then
P_TeleportMove(player.mo, player.mo.x, player.mo.y, player.mo.z+16*FRACUNIT)
elseif (player.mo.z > 5248*FRACUNIT) then
P_TeleportMove(player.mo, player.mo.x, player.mo.y, player.mo.z-16*FRACUNIT)
end
//If all is well, end repositioning
if (player.mo.x >= 11840*FRACUNIT)
and (player.mo.x <= 14624*FRACUNIT)
and (player.mo.y == 2144*FRACUNIT)
and (player.mo.z >= 3072*FRACUNIT)
and (player.mo.z <= 5248*FRACUNIT) then
player.mo.flags = $1 & ~(MF_NOGRAVITY|MF_NOBLOCKMAP)
player.mo.flags2 = $1|MF2_TWOD
player.mo.angle = ANGLE_180
player.powers[pw_nocontrol] = 0
player.mo.momx = 0
player.mo.momy = 0
player.mo.momz = 1*FRACUNIT
player.mo.reposition = nil
end
end
//Should the player be killed from the toxic water?
if (P_ThingOnSpecial3DFloor(player.mo)) then
if (P_ThingOnSpecial3DFloor(player.mo).tag == 395)
and (player.health <= 1)
and (player.mo.health <= 1) then
P_DamageMobj(player.mo, nil, nil, 1)
end
end
end
end
//If it's a multiplayer game or the player is Tails, close off the Tails section
if not (mo.closecheck) then
for player in players.iterate
if (player.mo and player.mo.skin == "tails") then
mo.someoneistails = true
end
end
if (multiplayer)
or (mo.someoneistails) then
A_LinedefExecute(mo, 404, 0)
end
end
mo.closecheck = true
end,MT_GREATDIVIDE1MARK)
//GDZ1 Crystal Snailers repel the player upon getting hit
addHook("MobjDamage", function(target, inflictor, source, damage)
if inflictor.type == MT_PLAYER then
inflictor.momx = -1*$1
inflictor.momy = -1*$1
end
end, MT_CRYSTALSNAILER)
//GDZ1 Tails arrow-spawn thinker
addHook("MobjThinker", function(mo)
//Initialize player-has-grabbed-it variable
if mo.playergrabbed == nil then
mo.playergrabbed = false
end
//Initialize player-used-to-be-grabbing-it variable
if mo.playergrabbedpast == nil then
mo.playergrabbedpast = false
end
//Check to determine if there is a player that has grabbed onto Tails
A_FindTracer(mo, MT_PLAYER, 0)
if mo.tracer != nil then
if mo.tracer.valid == true then
if mo.tracer.tracer == mo then
mo.playergrabbed = true
end
end
end
//If a player has just grabbed it, spawn the arrow control thingy
if (mo.playergrabbed == true)
and (mo.playergrabbedpast == false) then
P_SpawnMobj(mo.x, mo.y, mo.z, MT_TAILSARROWS)
end
//Set the variable that determines whether the missile was just being ridden next tic
mo.playergrabbedpast = mo.playergrabbed
end, MT_BLACKEGGMAN_MISSILE)
//GDZ1 Tails arrows thinker
addHook("MobjThinker", function(mo)
//If a Tails exists, follow it. If not, destroy self
A_FindTarget(mo,MT_BLACKEGGMAN_MISSILE,0)
if mo.target != nil then
local an = mo.target.angle
P_TeleportMove(mo, mo.target.x-1*cos(an), mo.target.y-1*sin(an), mo.target.z)
else
P_RemoveMobj(mo)
end
end, MT_TAILSARROWS)