-- Increased only on gameplay-changing updates local version = 6 if GRAPLE_VERSION and GRAPLE_VERSION < version then error("older version of graple lua is loaded, this may lead to problems") elseif GRAPLE_VERSION then print("\131NOTICE:\128 graple lua is already loaded") return end rawset(_G, "GRAPLE_VERSION", version) -- Max distance for grapling local DEFAULT_GRAPLE_DISTANCE = FRACUNIT*2000 -- For Hooke's law local DEFAULT_GRAPLE_K = FRACUNIT/16 -- Check player angle by default local DEFAULT_CHECK_ANGLE = true -- Default angle for sight checking local DEFAULT_CHECK_ANGLE_VALUE = 0 -- While i made few vars customizable, those bellow are still hardcoded. Not -- sure if i need to make them customizable too. If you are reading this script -- and want to customize those, just ask me on the mb.srb2.org and i'll make -- update, so all maps with graple script will still be compatible (as long as -- their authors update their copy of script :P) -- If we are grapling and holding accel button, accelerate in look direction for -- more control local GRAPLE_ACCEL = FRACUNIT*2/3 local GRAPLE_INDICATOR_DIST = FRACUNIT*60 freeslot("MT_DEFAULT_GRAPLE_ROPE") mobjinfo[MT_DEFAULT_GRAPLE_ROPE] = { spawnstate = S_RING, -- Ye its just a ring rn spawnhealth = 1000, -- No idea what is dis lol radius = FRACUNIT, -- Not sure if i need it height = FRACUNIT, -- Same dispoffset = 0, -- Same flags = MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT, } freeslot("MT_DEFAULT_GRAPLE_INDICATOR") mobjinfo[MT_DEFAULT_GRAPLE_INDICATOR] = { spawnstate = S_RING, -- Ye its just a ring rn spawnhealth = 1000, -- No idea what is dis lol radius = FRACUNIT, -- Not sure if i need it height = FRACUNIT, -- Same dispoffset = 0, -- Same flags = MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT, } -- XR_Defs - eXtended R_Defs -- Distance in 3d. Not sure why srb2 doesn't have something similar local function XR_Point3ToDist2(x1, y1, z1, x2, y2, z2) return FixedHypot(FixedHypot(x1-x2, y1-y2), z1 - z2) end -- Normalize 3d vector local function XR_Vec3Normalize(x, y, z) local dist = XR_Point3ToDist2(x, y, z, 0, 0, 0) return FixedDiv(x, dist), FixedDiv(y, dist), FixedDiv(z, dist) end -- Multiply vector by num local function XR_Vec3Scale(x, y, z, num) return FixedMul(x, num), FixedMul(y, num), FixedMul(z, num) end -- Convinient function to calculate distance between 2 mobjs local function XR_DistBetweenMobj(mo1, mo2) return XR_Point3ToDist2(mo1.x, mo1.y, mo1.z, mo2.x, mo2.y, mo2.z) end -- Calculate direction from one mobj to another. local function XR_DirToMobj(mo1, mo2) return XR_Vec3Normalize(mo2.x - mo1.x, mo2.y - mo1.y, mo2.z - mo1.z) end -- Is player able to graple? It only checks for conditions such as player is -- not standing on the ground, and it ignores is there actually a graple point -- nearby local function GRAPLE_CanGraple(player) return not P_IsObjectOnGround(player.mo) and not player.deadtimer and not player.kartstuff[k_spinouttimer] -- Comment that out to have speen grapling and not player.kartstuff[k_wipeoutslow] -- ^ and not player.kartstuff[k_respawn] end -- Spawn mobjs used for displaying a "rope" local function GRAPLE_SpawnRope(pmo, graple) local list = {} local dirx, diry, dirz = XR_DirToMobj(pmo, graple) local dist = XR_DistBetweenMobj(pmo, graple) local part_count = 10 for i = 1, part_count do local offx, offy, offz = XR_Vec3Scale(dirx, diry, dirz, dist*i/part_count) table.insert(list, P_SpawnMobj(pmo.x + offx, pmo.y + offy, pmo.z + offz, graple.MT_GRAPLE_ROPE or MT_DEFAULT_GRAPLE_ROPE)) end return list end -- Update rope mobjs local function GRAPLE_UpdateRope(pmo, graple, part_list) local dirx, diry, dirz = XR_DirToMobj(pmo, graple) local dist = XR_DistBetweenMobj(pmo, graple) for i = 1, #part_list do local offx, offy, offz = XR_Vec3Scale(dirx, diry, dirz, dist*i/#part_list) local part = part_list[i] P_MoveOrigin(part, pmo.x + offx, pmo.y + offy, pmo.z + offz) end end -- Remove rope mobjs local function GRAPLE_RemoveRope(part_list) if not part_list then return end for i = 1, #part_list do if part_list[i].valid then P_RemoveMobj(part_list[i]) end end end -- Aim towards closest graple point so player know where is graple point local function GRAPLE_UpdateGrapleIndicator(graple_indicator) if not graple_indicator or not graple_indicator.valid then return end local player = graple_indicator.source_player local target = graple_indicator.target if not player or player.spectator or not player.valid then P_RemoveMobj(graple_indicator) return end if target == nil then local offset = GRAPLE_INDICATOR_DIST + FixedMul(GRAPLE_INDICATOR_DIST/8, sin(leveltime*ANG10)) P_MoveOrigin(graple_indicator, player.mo.x, player.mo.y, player.mo.z + FixedMul(offset, mapobjectscale)) if player.cmd.buttons & BT_BRAKE and GRAPLE_CanGraple(player) and leveltime % 2 == 0 then graple_indicator.color = SKINCOLOR_RED else graple_indicator.color = SKINCOLOR_GREY end else local dirx, diry, dirz = XR_DirToMobj(graple_indicator, target) local offx, offy, offz = XR_Vec3Scale(dirx, diry, dirz, FixedMul(GRAPLE_INDICATOR_DIST, mapobjectscale)*2) graple_indicator.color = SKINCOLOR_GREEN P_MoveOrigin( graple_indicator, player.mo.x + offx, player.mo.y + offy, player.mo.z + offz) end end -- Set mobj types for indicator and rope function A_SetGrapleVisuals(graple, var1, var2) graple.MT_GRAPLE_INDICATOR = var1 graple.MT_GRAPLE_ROPE = var2 end -- Set values for minimum graple distance and Hooke's law parameter function A_SetGrapleValues(graple, var1, var2) graple.GRAPLE_DISTANCE = var1 graple.GRAPLE_K = var2 end -- Set sounds for grapling function A_SetGrapleSounds(graple, var1, var2) graple.GRAPLE_START_SOUND = var1 graple.GRAPLE_END_SOUND = var2 end -- Set/enable/disable angle for angle checking function A_SetGrapleAngleCheck(graple, var1, var2) graple.GRAPLE_CHECK_ANGLE = var1 graple.GRAPLE_CHECK_ANGLE_VALUE = cos(var2*ANG1) end -- Set closest possible graple point for each player. function A_GraplePointThinker(graple) local GRAPLE_DISTANCE = graple.GRAPLE_DISTANCE or DEFAULT_GRAPLE_DISTANCE local GRAPLE_CHECK_ANGLE = DEFAULT_CHECK_ANGLE local GRAPLE_CHECK_ANGLE_VALUE = graple.GRAPLE_CHECK_ANGLE_VALUE or DEFAULT_CHECK_ANGLE_VALUE if graple.GRAPLE_CHECK_ANGLE ~= nil then GRAPLE_CHECK_ANGLE = graple.GRAPLE_CHECK_ANGLE end for player in players.iterate do -- Spawn graple indicator for player if they don't have one. That will -- happen only if map has at least one graple point, so if it appeared -- then player can figure out that this map has grapling mechanic if not player.spectator and not (player.graple_indicator and player.graple_indicator.valid) then player.graple_indicator = P_SpawnMobj( player.mo.x, player.mo.y, player.mo.z, graple.MT_GRAPLE_INDICATOR or MT_DEFAULT_GRAPLE_INDICATOR) player.graple_indicator.source_player = player player.graple_indicator.colorized = true if not player.seen_graple_tip then chatprintf(player, "\131*This map uses grapling mechanic! Use BRAKE key near graple point to start grapling.") player.seen_graple_tip = true end end -- Do not waste cpu on players that are in grapling state. player.graple -- may still have invalid non-nil reference if map changed when player -- was grapling, but thats fine since player thinker will handle that -- and reset player.graple to nil if not player.spectator and not player.graple then local dist = XR_DistBetweenMobj(player.mo, graple) local pnormx, pnormy = cos(player.mo.angle), sin(player.mo.angle) local gnormx, gnormy = XR_Vec3Normalize(graple.x - player.mo.x, graple.y - player.mo.y, 0) local dotprod = FixedMul(pnormx, gnormx) + FixedMul(pnormy, gnormy) if dist < GRAPLE_DISTANCE then if not GRAPLE_CHECK_ANGLE or dotprod >= GRAPLE_CHECK_ANGLE_VALUE then if player.closest_graple == nil or dist < player.closest_graple_dist then player.closest_graple = graple player.closest_graple_dist = dist player.graple_indicator.target = graple end end end end end end addHook("MobjThinker", function(pmo) local player = pmo.player GRAPLE_UpdateGrapleIndicator(player.graple_indicator) if player.cmd.buttons & BT_BRAKE and GRAPLE_CanGraple(player) then local graple = player.graple local graple_dist = player.graple_dist -- Start graple if we haven't already if not player.graple or not player.graple.valid then player.graple = player.closest_graple player.graple_dist = player.closest_graple_dist -- No graple points are avaible. I'm falling! if not player.graple or not player.graple.valid then return end graple = player.graple graple_dist = player.graple_dist player.graple_rope = GRAPLE_SpawnRope(pmo, graple) S_StartSound(player.graple_indicator, graple.GRAPLE_START_SOUND or sfx_s3k4a) end local dist = XR_DistBetweenMobj(pmo, graple) local dx = dist - graple_dist -- Hooke's law local force = FixedMul(max(dx, 0), graple.GRAPLE_K or DEFAULT_GRAPLE_K) -- I'd want to just write XR_Vec3Scale(XR_DirToMobj(...), force), but it -- seems lua doesn't support that local dirx, diry, dirz = XR_DirToMobj(pmo, graple) -- accel = force/mass. For now, i'll consider mass 1 local accx, accy, accz = XR_Vec3Scale(dirx, diry, dirz, force) pmo.momx = $ + accx pmo.momy = $ + accy pmo.momz = $ + accz -- Lets give player more control over grapling if player.cmd.buttons & BT_ACCELERATE then local acceldirx = cos(pmo.angle) local acceldiry = sin(pmo.angle) pmo.momx = $ + FixedMul(GRAPLE_ACCEL, acceldirx) pmo.momy = $ + FixedMul(GRAPLE_ACCEL, acceldiry) end GRAPLE_UpdateRope(pmo, graple, player.graple_rope) -- Tie rope up, without this grapling is not that fun to use if dx < 0 then player.graple_dist = dist end else -- Play optional sound upon releasing the graple if player.graple and player.graple.valid then if player.graple.GRAPLE_END_SOUND then S_StartSound(player.graple_indicator, player.graple.GRAPLE_END_SOUND) end end player.graple = nil player.graple_dist = nil if not player.closest_graple and player.graple_indicator and player.graple_indicator.valid then player.graple_indicator.target = nil end -- GRAPLE_RemoveRope(nil) is safe (just like free(NULL) :P) player.graple_rope = GRAPLE_RemoveRope(player.graple_rope) player.closest_graple = nil player.closest_graple_dist = nil end end, MT_PLAYER)