-- Increased only on gameplay-changing updates -- Was reset because of 2.0 update -- Older "graple" luas should still work local version = 1 if GRAPPLE_VERSION and GRAPPLE_VERSION < version then error("older version of grapple lua is loaded, this may lead to problems") elseif GRAPPLE_VERSION then print("\131NOTICE:\128 grapple lua is already loaded") return end rawset(_G, "GRAPPLE_VERSION", version) freeslot("MT_DEFAULT_GRAPPLE_ROPE") freeslot("MT_DEFAULT_GRAPPLE_INDICATOR") local GRAPPLE_INDICATOR_DIST = FRACUNIT*60 local GRAPPLE_DEFAULTS = { distance = FRACUNIT*2000, stiffness = FRACUNIT/16, angle_check = true, angle_check_cos = 0, cut_vertical_momentum = true, max_vertical_momentum = 40*FRACUNIT, player_accel = 2*FRACUNIT/3, indicator_mobj_type = MT_DEFAULT_GRAPPLE_INDICATOR, rope_mobj_type = MT_DEFAULT_GRAPPLE_ROPE, grapple_start_sound = sfx_s3k4a, grapple_end_sound = nil, } local cv_showgrappletip = CV_RegisterVar({ name = "showgrappletip", defaultvalue = "Off", possiblevalue = CV_OnOff, }) mobjinfo[MT_DEFAULT_GRAPPLE_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, } mobjinfo[MT_DEFAULT_GRAPPLE_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, } local function setFields(t1, t2) for k, v in pairs(t2) do t1[k] = v end end -------------------------------------------------------------------------------- -- 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 -------------------------------------------------------------------------------- -- Grapple-related functions -------------------------------------------------------------------------------- -- Set grapple attributes local function GRAPPLE_SetAttributes(mo, grapple_info) if not grapple_info then return end mo.grapple_info = mo.grapple_info or {} setFields(mo.grapple_info, grapple_info) end -- Reset all attributes to their default values local function GRAPPLE_SetDefaultAttributes(mo) GRAPPLE_SetAttributes(mo, GRAPPLE_DEFAULTS) end -- Is player able to grapple? It only checks for conditions such as player is -- not standing on the ground, and it ignores is there actually a grapple point -- nearby local function GRAPPLE_CanGrapple(player) return not P_IsObjectOnGround(player.mo) and not player.deadtimer and not player.kartstuff[k_spinouttimer] -- Comment that out to have speen grappling and not player.kartstuff[k_wipeoutslow] -- ^ and not player.kartstuff[k_respawn] end -- Spawn mobjs used for displaying a "rope" local function GRAPPLE_SpawnRope(pmo, grapple) local list = {} local dirx, diry, dirz = XR_DirToMobj(pmo, grapple) local dist = XR_DistBetweenMobj(pmo, grapple) local part_count = 10 local ROPE_TYPE = grapple.grapple_info.rope_mobj_type 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, ROPE_TYPE)) end return list end -- Update rope mobjs local function GRAPPLE_UpdateRope(pmo, grapple, part_list) local dirx, diry, dirz = XR_DirToMobj(pmo, grapple) local dist = XR_DistBetweenMobj(pmo, grapple) 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 GRAPPLE_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 grapple point so player know where is grapple point local function GRAPPLE_UpdateGrappleIndicator(grapple_indicator) if not grapple_indicator or not grapple_indicator.valid then return end local player = grapple_indicator.source_player local target = grapple_indicator.target if not (player and player.valid and player.mo and player.mo.valid) then P_RemoveMobj(grapple_indicator) return end local pmo = player.mo if target == nil or player.releasegrapple then local offset = GRAPPLE_INDICATOR_DIST + FixedMul(GRAPPLE_INDICATOR_DIST/8, sin(leveltime*ANG10)) P_MoveOrigin(grapple_indicator, pmo.x, pmo.y, pmo.z + FixedMul(offset, mapobjectscale)) grapple_indicator.frame = grapple_indicator.frame | FF_TRANS50 if player.cmd.buttons & BT_BRAKE and GRAPPLE_CanGrapple(player) and leveltime % 2 == 0 then grapple_indicator.color = SKINCOLOR_RED else grapple_indicator.color = SKINCOLOR_GREY end else local dirx, diry, dirz = XR_DirToMobj(pmo, target) local offx, offy, offz = XR_Vec3Scale(dirx, diry, dirz, FixedMul(GRAPPLE_INDICATOR_DIST, mapobjectscale)*2) grapple_indicator.frame = grapple_indicator.frame & ~FF_TRANS50 grapple_indicator.color = SKINCOLOR_GREEN P_MoveOrigin( grapple_indicator, pmo.x + offx, pmo.y + offy, pmo.z + offz) end end -- Set closest possible grapple point for each player. function A_GrapplePointThinker(grapple) if not grapple.grapple_info then GRAPPLE_SetDefaultAttributes(grapple) end local GRAPPLE_DISTANCE = grapple.grapple_info.distance local GRAPPLE_CHECK_ANGLE = grapple.grapple_info.angle_check local GRAPPLE_CHECK_ANGLE_VALUE = grapple.grapple_info.angle_check_cos local GRAPPLE_INDICATOR_TYPE = grapple.grapple_info.indicator_mobj_type for player in players.iterate do if not (player.mo and player.mo.valid) then continue end local pmo = player.mo -- Spawn grapple indicator for player if they don't have one. That will -- happen only if map has at least one grapple point, so if it appeared -- then player can figure out that this map has grappling mechanic if not (player.grapple_indicator and player.grapple_indicator.valid) then player.grapple_indicator = P_SpawnMobj( pmo.x, pmo.y, pmo.z, GRAPPLE_INDICATOR_TYPE) player.grapple_indicator.source_player = player player.grapple_indicator.colorized = true if cv_showgrappletip.value then chatprintf(player, "\131*This map uses grappling mechanic! Use BRAKE key near grapple point to start grappling.", true) end end -- Do not waste cpu on players that are in grappling state. player.grapple -- may still have invalid non-nil reference if map changed when player -- was grappling, but thats fine since player thinker will handle that -- and reset player.grapple to nil if not player.spectator and not player.grapple and abs(pmo.x - grapple.x) <= GRAPPLE_DISTANCE -- A little optimization for grapple points that are way too far away and abs(pmo.y - grapple.y) <= GRAPPLE_DISTANCE then -- That way we don't need to do more heavy calculations local dist = XR_DistBetweenMobj(player.mo, grapple) local pnormx, pnormy = cos(pmo.angle), sin(pmo.angle) local gnormx, gnormy = XR_Vec3Normalize(grapple.x - pmo.x, grapple.y - pmo.y, 0) local dotprod = FixedMul(pnormx, gnormx) + FixedMul(pnormy, gnormy) if dist < GRAPPLE_DISTANCE then if not GRAPPLE_CHECK_ANGLE or dotprod >= GRAPPLE_CHECK_ANGLE_VALUE then if player.closest_grapple == nil or dist < player.closest_grapple_dist then player.closest_grapple = grapple player.closest_grapple_dist = dist player.grapple_indicator.target = grapple end end end end end end local function GRAPPLE_RegisterGrapplePoint(name, attributes) local mt_name = "MT_"..name:upper() freeslot(mt_name) local mt = _G[mt_name] addHook("MobjSpawn", function(mo) GRAPPLE_SetDefaultAttributes(mo) GRAPPLE_SetAttributes(mo, attributes) end, mt) addHook("MobjThinker", A_GrapplePointThinker, mt) end addHook("MobjThinker", function(pmo) local player = pmo.player if player.releasegrapple and not (player.cmd.buttons & BT_BRAKE) then player.releasegrapple = false end if player.cmd.buttons & BT_BRAKE and not player.releasegrapple and GRAPPLE_CanGrapple(player) then local grapple = player.grapple local grapple_dist = player.grapple_dist -- Start grapple if we haven't already if not player.grapple or not player.grapple.valid then player.grapple = player.closest_grapple player.grapple_dist = player.closest_grapple_dist -- No grapple points are avaible. I'm falling! if not player.grapple or not player.grapple.valid then return end grapple = player.grapple grapple_dist = player.grapple_dist player.grapple_rope = GRAPPLE_SpawnRope(pmo, grapple) S_StartSound(player.grapple_indicator, grapple.grapple_info.grapple_start_sound) end local dist = XR_DistBetweenMobj(pmo, grapple) local dx = dist - grapple_dist -- Hooke's law local force = FixedMul(max(dx, 0), grapple.grapple_info.stiffness) -- 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, grapple) -- 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 grappling if player.cmd.buttons & BT_ACCELERATE then local acceldirx = cos(pmo.angle) local acceldiry = sin(pmo.angle) local accel = grapple.grapple_info.player_accel pmo.momx = $ + FixedMul(accel, acceldirx) pmo.momy = $ + FixedMul(accel, acceldiry) end -- Tie rope up, without this grappling is not that fun to use if dx < 0 then player.grapple_dist = dist end else -- Actions upon releasing grapple if player.grapple and player.grapple.valid then local GRAPPLE_ZMOM_CUTOFF = player.grapple.grapple_info.cut_vertical_momentum local GRAPPLE_ZMOM_CUTOFF_VALUE = player.grapple.grapple_info.max_vertical_momentum if GRAPPLE_ZMOM_CUTOFF and abs(pmo.momz) >= GRAPPLE_ZMOM_CUTOFF_VALUE then if pmo.momz < 0 then GRAPPLE_ZMOM_CUTOFF_VALUE = GRAPPLE_ZMOM_CUTOFF_VALUE * -1 end local diff = abs(pmo.momz - GRAPPLE_ZMOM_CUTOFF_VALUE) pmo.momz = GRAPPLE_ZMOM_CUTOFF_VALUE -- Uhh, maybe i should add XR_Vec2* functions or something, but -- i'm too lazy for that :P local momxdir, momydir = XR_Vec3Normalize(pmo.momx, pmo.momy, 0) local addmomx, addmomy = XR_Vec3Scale(momxdir, momydir, 0, diff) pmo.momx = pmo.momx + addmomx pmo.momy = pmo.momy + addmomy S_StartSound(pmo, sfx_spdpad) end if player.grapple.grapple_info.grapple_end_sound then S_StartSound(player.grapple_indicator, player.grapple.grapple_info.grapple_end_sound) end end player.grapple = nil player.grapple_dist = nil if not player.closest_grapple and player.grapple_indicator and player.grapple_indicator.valid then player.grapple_indicator.target = nil end -- GRAPPLE_RemoveRope(nil) is safe (just like free(NULL) :P) player.grapple_rope = GRAPPLE_RemoveRope(player.grapple_rope) player.closest_grapple = nil player.closest_grapple_dist = nil end end, MT_PLAYER) addHook("ThinkFrame", function() for player in players.iterate do GRAPPLE_UpdateGrappleIndicator(player.grapple_indicator) local pmo = player.mo local grapple = player.grapple local rope_list = player.grapple_rope if pmo and pmo.valid and grapple and grapple.valid and rope_list then GRAPPLE_UpdateRope(pmo, grapple, rope_list) end end end) -- Great for funni semi-auto sections - this will release grapple for player. -- Can be used in 180 turns or something like that addHook("LinedefExecute", function(line, mobj, sector) if mobj.player and mobj.player.valid then mobj.player.releasegrapple = true end end, "RELEASEGRAPPLE") rawset(_G, "grapple", { registerGrapplePoint = GRAPPLE_RegisterGrapplePoint, setAttributes = GRAPPLE_SetAttributes, setDefaultAttributes = GRAPPLE_SetDefaultAttributes, })