Skip to content

Commit 169cb05

Browse files
committed
Refactor grapple to use rigid, unbreakable Verlet rope physics
Implements a new grapple rope system based on pure Verlet constraints for robust, non-stretchy behavior. - Overhauls rope physics, removing old spring/force systems and stretch mechanics. - Makes the rope virtually unbreakable with an extremely high tension threshold (500% stretch). - Centralizes rope length control and simplifies related logic. - Enhances actor and target MO protection systems to ensure stability with the new rigid physics. - Updates debug rendering to display detailed rope state information. Make grapple rope rigid and virtually unbreakable Replaces the previous spring-based system with a pure Verlet constraint model, resulting in a non-stretchy and highly robust rope. The rope is now virtually unbreakable, only snapping at an extremely high tension threshold (500% stretch). Rope length control is centralized and simplified. Actor and target MO protection systems are enhanced to ensure stability with the new rigid physics. Debug rendering is updated to display detailed rope state information.
1 parent 10300a8 commit 169cb05

File tree

5 files changed

+702
-401
lines changed

5 files changed

+702
-401
lines changed

Data/Base.rte/Devices/Tools/GrappleGun/Grapple.lua

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ function Create(self)
2323
self.fireVel = 40 -- This immediately overwrites the .ini FireVel
2424
self.maxLineLength = 400 -- Shorter rope for faster gameplay
2525
self.setLineLength = 0
26-
self.lineStrength = 40 -- How much "force" the rope can take before breaking
26+
self.lineStrength = 10000 -- EXTREMELY HIGH force threshold - virtually unbreakable (was 120)
2727

2828
self.limitReached = false
29-
self.stretchMode = false -- Alternative elastic pull mode a là Liero
30-
self.stretchPullRatio = 0.01 -- How much the rope stretches when pulling in stretch mode
29+
self.stretchMode = false -- Disabled for rigid rope behavior
30+
self.stretchPullRatio = 0.0 -- No stretching allowed for rigid rope
3131
self.pieSelection = 0 -- 0 is nothing, 1 is full retract, 2 is partial retract, 3 is partial extend, 4 is full extend
3232

3333
self.climbDelay = 8 -- Faster climbing for shorter rope
@@ -45,17 +45,14 @@ function Create(self)
4545
-- Rope physics variables from VelvetGrapple
4646
self.currentLineLength = 0
4747
self.longestLineLength = 0
48-
self.cablespring = 0.15 -- VelvetGrapple constraint stiffness
48+
self.cablespring = 0.01 -- Very low for completely rigid rope behavior (was 0.05)
4949

5050
-- Dynamic rope segment calculation variables
5151
self.minSegments = 1 -- Minimum number of segments
5252
self.maxSegments = 500 -- Maximum number of segments
5353
self.segmentLength = 12 -- Target length per segment (increased for better performance)
5454
self.currentSegments = self.minSegments -- Current number of segments
5555

56-
-- Verlet physics friction for stability
57-
self.usefriction = 0.99 -- Matches VelvetGrapple
58-
5956
-- Mousewheel control variables
6057
self.shiftScrollSpeed = 8.0 -- Faster rope control with Shift+Mousewheel
6158

@@ -125,9 +122,9 @@ function Update(self)
125122
self.ToDelete = false
126123
self.ToSettle = false
127124

128-
-- Make sure we have a minimum viable rope length to avoid issues
129-
if self.actionMode == 1 and self.currentLineLength < 1 then
130-
self.currentLineLength = math.max(1, SceneMan:ShortestDistance(self.parent.Pos, self.Pos, self.mapWrapsX).Magnitude)
125+
-- Make sure we have valid rope data, but allow zero length
126+
if self.actionMode == 1 and self.currentLineLength < 0 then
127+
self.currentLineLength = 0 -- Allow zero length compression
131128
end
132129

133130
-- Update line length when in flight
@@ -172,8 +169,20 @@ function Update(self)
172169
-- Attached mode - run full physics simulation
173170
RopePhysics.updateRopePhysics(self, startPos, endPos, self.currentLineLength)
174171

175-
-- Apply constraints to maintain rope structure and length
176-
RopePhysics.applyRopeConstraints(self, self.currentLineLength)
172+
-- Apply constraints and check for rope breaking (extremely high threshold)
173+
local ropeBreaks = RopePhysics.applyRopeConstraints(self, self.currentLineLength)
174+
if ropeBreaks or self.shouldBreak then
175+
-- Rope snapped due to EXTREME tension (500% stretch)
176+
self.ToDelete = true
177+
if self.parent and self.parent:IsPlayerControlled() then
178+
-- Add screen shake and sound effect when rope breaks
179+
FrameMan:SetScreenScrollSpeed(10.0) -- More dramatic shake for extreme break
180+
if self.returnSound then
181+
self.returnSound:Play(self.parent.Pos)
182+
end
183+
end
184+
return -- Exit early since rope is breaking
185+
end
177186
end
178187

179188
-- Special handling for attached targets (MO grabbing)
@@ -205,13 +214,6 @@ function Update(self)
205214

206215
-- Draw the rope using the renderer module
207216
RopeRenderer.drawRope(self, player)
208-
209-
-- Show rope tension indicator when necessary
210-
RopeRenderer.showTensionIndicator(self, player)
211-
212-
-- Show debug info temporarily to help diagnose issues
213-
local debugPos = self.Pos + Vector(10, -30) -- Define a position for debug text
214-
RopeRenderer.showDebugInfo(self, player, debugPos)
215217

216218
-- Update lineVec and lineLength based on current positions
217219
self.lineVec = SceneMan:ShortestDistance(self.parent.Pos, self.Pos, self.mapWrapsX)
@@ -223,17 +225,29 @@ function Update(self)
223225
self.Pos.Y = self.apy[self.currentSegments]
224226
end
225227

226-
-- Update current line length based on action mode
228+
-- Update current line length based on action mode - CENTRALIZED CONTROL
227229
if self.actionMode == 1 and self.limitReached == false then
228230
-- Always update rope length while in flight - rope should be tight
229231
self.currentLineLength = self.lineLength
232+
self.setLineLength = self.currentLineLength
230233
elseif self.actionMode > 1 then
231-
-- When attached, maintain set line length for physics constraints
232-
-- currentLineLength should only be updated by player input or automatic climbing
234+
-- When attached, currentLineLength is controlled by input/auto-climbing
235+
-- Ensure it stays within bounds
236+
self.currentLineLength = math.max(10, math.min(self.currentLineLength, self.maxLineLength))
237+
self.setLineLength = self.currentLineLength
233238
end
234239

235-
-- Check if line length exceeds maximum
236-
RopeStateManager.checkLineLengthUpdate(self)
240+
-- Single length limit check - removed redundant checkLineLengthUpdate call
241+
if self.currentLineLength > self.maxLineLength then
242+
self.currentLineLength = self.maxLineLength
243+
self.setLineLength = self.maxLineLength
244+
if not self.limitReached then
245+
self.limitReached = true
246+
self.clickSound:Play(self.parent.Pos)
247+
end
248+
else
249+
self.limitReached = false
250+
end
237251

238252
if self.parentGun and self.parentGun.ID ~= rte.NoMOID then
239253
self.parent = ToMOSRotating(MovableMan:GetMOFromID(self.parentGun.RootID))
@@ -340,15 +354,12 @@ function Update(self)
340354
-- Process input based climbing
341355
RopeInputController.handleRopePulling(self)
342356

343-
-- Process terrain pull physics
344-
if self.actionMode == 2 and RopeStateManager.applyTerrainPullPhysics(self) then
345-
self.ToDelete = true
346-
end
357+
-- DISABLE force-based physics - using pure Verlet constraint system instead
358+
-- The RopePhysics.applyRopeConstraints handles all position constraints
359+
-- No need for additional spring forces that conflict with rigid constraints
347360

348-
-- Process MO pull physics
349-
if self.actionMode == 3 and RopeStateManager.applyMOPullPhysics(self) then
350-
self.ToDelete = true
351-
end
361+
-- UNBREAKABLE ROPE: No automatic unhooking due to target destruction
362+
-- Rope remains attached even if target MO is destroyed for maximum persistence
352363
end
353364

354365
-- Check if we should unhook via double-tap mechanic

Data/Base.rte/Devices/Tools/GrappleGun/Scripts/RopeInputController.lua

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
-- Grapple Gun Input Controller Module
2-
-- Handles all user input related to grapple rope control
2+
-- Handles user input for rope control with pure constraint-based physics
3+
-- No velocity manipulation, no force application - only rope length control
34

45
local RopeInputController = {}
56

@@ -134,18 +135,10 @@ function RopeInputController.handleDirectionalControl(grappleInstance, controlle
134135

135136
if controller:IsMouseControlled() == false then
136137
if controller:IsState(Controller.HOLD_UP) then
137-
if grappleInstance.currentLineLength > grappleInstance.climbInterval and terrCheck == false then
138+
if grappleInstance.currentLineLength > grappleInstance.climbInterval then
138139
grappleInstance.climb = 1
139-
elseif terrCheck ~= false then
140-
-- Try to nudge past terrain
141-
local nudge = math.sqrt(grappleInstance.lineLength + grappleInstance.parent.Radius) /
142-
(10 + grappleInstance.parent.Vel.Magnitude)
143-
local aimvec = Vector(grappleInstance.lineVec.Magnitude, 0)
144-
:SetMagnitude(nudge)
145-
:RadRotate((grappleInstance.lineVec.AbsRadAngle +
146-
grappleInstance.parent:GetAimAngle(true))/2 +
147-
grappleInstance.parent.FlipFactor * 0.7)
148-
grappleInstance.parent.Vel = grappleInstance.parent.Vel + aimvec
140+
-- Pure position-based system - no direct velocity manipulation
141+
-- Terrain obstacles are handled by Verlet constraint system
149142
end
150143
end
151144

@@ -260,24 +253,14 @@ function RopeInputController.handleAutoRetraction(grappleInstance, terrCheck)
260253
grappleInstance.climbTimer:Reset()
261254

262255
if grappleInstance.pieSelection == 0 and grappleInstance.parentGun:IsActivated() then
263-
if grappleInstance.currentLineLength > grappleInstance.autoClimbIntervalA and terrCheck == false then
256+
if grappleInstance.currentLineLength > grappleInstance.autoClimbIntervalA then
264257
grappleInstance.currentLineLength = grappleInstance.currentLineLength - (grappleInstance.autoClimbIntervalA/parentForces)
265258
grappleInstance.setLineLength = grappleInstance.currentLineLength
259+
-- Pure position-based system - no velocity manipulation or terrain nudging
260+
-- Verlet constraints handle all physical interactions
266261
else
267262
grappleInstance.parentGun:RemoveNumberValue("GrappleMode")
268263
grappleInstance.pieSelection = 0
269-
270-
if terrCheck ~= false then
271-
-- Try to nudge past terrain
272-
local nudge = math.sqrt(grappleInstance.lineLength + grappleInstance.parent.Radius) /
273-
(10 + grappleInstance.parent.Vel.Magnitude)
274-
local aimvec = Vector(grappleInstance.lineVec.Magnitude, 0)
275-
:SetMagnitude(nudge)
276-
:RadRotate((grappleInstance.lineVec.AbsRadAngle +
277-
grappleInstance.parent:GetAimAngle(true))/2 +
278-
grappleInstance.parent.FlipFactor * 0.7)
279-
grappleInstance.parent.Vel = grappleInstance.parent.Vel + aimvec
280-
end
281264
end
282265
end
283266
end
@@ -288,9 +271,10 @@ function RopeInputController.handleAutoRetraction(grappleInstance, terrCheck)
288271

289272
if grappleInstance.pieSelection == 1 then
290273
-- Full retract
291-
if grappleInstance.currentLineLength > grappleInstance.autoClimbIntervalA and terrCheck == false then
274+
if grappleInstance.currentLineLength > grappleInstance.autoClimbIntervalA then
292275
grappleInstance.currentLineLength = grappleInstance.currentLineLength - (grappleInstance.autoClimbIntervalA/parentForces)
293276
grappleInstance.setLineLength = grappleInstance.currentLineLength
277+
-- Pure position-based system - no terrain checking or velocity manipulation
294278
else
295279
grappleInstance.pieSelection = 0
296280
end

0 commit comments

Comments
 (0)