Making your first C Program

Status
Not open for further replies.
To code your first program in C I recommend that you use this code. You can download Dev-C++. After you type this code you need to compile it.

Code:
#include <stdio.h>

int main(void) {
  printf("Hello World!");
  getch();
}
 
Alright this tutorial is good.

Now look at my tutorial on how to sprite.

Step 1: Copy this sprite I made in 5 seconds without effort.

BvWXrF2.png


Congratulations! You now know how to sprite!
 
Excellent tutorials! Allow me to teach you guys how to map.

First you need a map editor. I recommend SRB2DB. Then create this map in it:

9p8e74b.png


Congratulations on learning how to make maps!
 
Even though I'm a relative novice at Lua myself, I feel that teaching people how to use it in the context of SRB2 is incredibly important to allow for the creation of more quality mods and scripts. First, you'll need to open up a WAD editor of your choice (my personal favorite is SLADE). Next, create a new lump in a blank WAD file and name it LUA_GAME. Now you'll need some code to play around with. Messing with this a little should be a good starting point for a beginner like you:

Code:
//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)
 
If somebody wants to learn how to code Hello World, there are thousands of much-better tutorials out there on the Internet than this one. This literally teaches nothing to anyone other than how to copy-paste, which presumably they already knew.
 
Status
Not open for further replies.

Who is viewing this thread (Total: 1, Members: 0, Guests: 1)

Back
Top