1
+ --- @diagnostic disable : undefined-global
2
+ -- Localize Cortex Command globals
3
+ local Timer = Timer
4
+ local PresetMan = PresetMan
5
+ local CreateMOSRotating = CreateMOSRotating
6
+ local IsActor = IsActor
7
+ local Actor = Actor
8
+ local ToMOSParticle = ToMOSParticle
9
+ local ToMOSprite = ToMOSprite
10
+ local PrimitiveMan = PrimitiveMan
11
+ local ActivityMan = ActivityMan
12
+ local MovableMan = MovableMan
13
+ local Vector = Vector
14
+ local Controller = Controller -- For Controller.BODY_PRONE etc.
15
+ local rte = rte
16
+
1
17
function Create (self )
2
- self .tapTimerAim = Timer ();
3
- self .tapTimerJump = Timer ();
4
- self .tapCounter = 0 ;
5
- self .didTap = false ;
6
- self .canTap = false ;
18
+ -- Timers and counters for tap-based controls (e.g., double-tap to retrieve hook)
19
+ self .tapTimerAim = Timer () -- Unused? Or intended for a different tap action.
20
+ self .tapTimerJump = Timer () -- Used for crouch-tap detection.
21
+ self .tapCounter = 0
22
+ -- self.didTap = false -- Seems unused, consider removing.
23
+ self .canTap = false -- Flag to register the first tap in a sequence.
7
24
8
- self .tapTime = 200 ;
9
- self .tapAmount = 2 ;
10
- self .guide = false ;
25
+ self .tapTime = 200 -- Max milliseconds between taps for them to count as a sequence.
26
+ self .tapAmount = 2 -- Number of taps required.
27
+
28
+ self .guide = false -- Whether to show the aiming guide arrow.
11
29
12
- self .arrow = CreateMOSRotating (" Grapple Gun Guide Arrow" );
30
+ -- Create the guide arrow MOSRotating. This is a visual aid.
31
+ -- Ensure "Grapple Gun Guide Arrow" preset exists and is a MOSRotating.
32
+ local arrowPreset = PresetMan :GetPreset (" Grapple Gun Guide Arrow" , " MOSRotating" , " Grapple Gun Guide Arrow" )
33
+ if arrowPreset and arrowPreset .ClassName == " MOSRotating" then
34
+ self .arrow = CreateMOSRotating (" Grapple Gun Guide Arrow" )
35
+ if self .arrow then
36
+ self .arrow .GlobalAccurateDelete = true -- Ensure it cleans up properly
37
+ end
38
+ else
39
+ self .arrow = nil -- Preset not found or incorrect type
40
+ -- Log an error or warning if preset is missing/incorrect
41
+ -- print("Warning: Grapple Gun Guide Arrow preset not found or incorrect type.")
42
+ end
13
43
end
14
44
15
45
function Update (self )
16
- local parent = self :GetRootParent ();
17
- if parent and IsActor (parent ) then
18
- if IsAHuman (parent ) then
19
- parent = ToAHuman (parent );
20
- elseif IsACrab (parent ) then
21
- parent = ToACrab (parent );
22
- else
23
- parent = ToActor (parent );
24
- end
25
- if parent :IsPlayerControlled () and parent .Status < Actor .DYING then
26
- local controller = parent :GetController ();
27
- local mouse = controller :IsMouseControlled ();
28
- -- Deactivate when equipped in BG arm to allow FG arm shooting
29
- if parent .EquippedBGItem and parent .EquippedItem then
30
- if parent .EquippedBGItem .ID == self .ID then
31
- self :Deactivate ();
32
- end
33
- end
34
-
35
- if self .Magazine then
36
- -- Double tapping crouch retrieves the hook
37
- if self .Magazine .Scale == 1 then
38
- self .StanceOffset = Vector (ToMOSprite (self :GetParent ()):GetSpriteWidth (), 1 );
39
- self .SharpStanceOffset = Vector (ToMOSprite (self :GetParent ()):GetSpriteWidth (), 1 );
40
- if controller and controller :IsState (Controller .BODY_PRONE ) then
41
- if self .canTap then
42
- controller :SetState (Controller .BODY_PRONE , false );
43
- self .tapTimerJump :Reset ();
44
- self .didTap = true ;
45
- self .canTap = false ;
46
- self .tapCounter = self .tapCounter + 1 ;
47
- end
48
- else
49
- self .canTap = true ;
50
- end
51
-
52
- if self .tapTimerJump :IsPastSimMS (self .tapTime ) then
53
- self .tapCounter = 0 ;
54
- else
55
- if self .tapCounter >= self .tapAmount then
56
- self :Activate ();
57
- self .tapCounter = 0 ;
58
- end
59
- end
60
- end
61
-
62
- -- A guide arrow appears at higher speeds
63
- if (self .Magazine .Scale == 0 and not controller :IsState (Controller .AIM_SHARP )) or parent .Vel :MagnitudeIsGreaterThan (6 ) then
64
- self .guide = true ;
65
- else
66
- self .guide = false ;
67
- end
68
- end
69
-
70
- if self .guide then
71
- local frame = 0 ;
72
- if parent .Vel :MagnitudeIsGreaterThan (12 ) then
73
- frame = 1 ;
74
- end
75
- local startPos = (parent .Pos + parent .EyePos + self .Pos )/ 3 ;
76
- local guidePos = startPos + Vector (parent .AimDistance + (parent .Vel .Magnitude ), 0 ):RadRotate (parent :GetAimAngle (true ));
77
- PrimitiveMan :DrawBitmapPrimitive (ActivityMan :GetActivity ():ScreenOfPlayer (controller .Player ), guidePos , self .arrow , parent :GetAimAngle (true ), frame );
78
- end
79
- else
80
- self :Deactivate ();
81
- end
82
-
83
- if self .Magazine then
84
- self .Magazine .RoundCount = 1 ;
85
- self .Magazine .Scale = 1 ;
86
- self .Magazine .Frame = 0 ;
87
- end
88
- end
46
+ local parent = self :GetRootParent ()
47
+
48
+ -- Ensure the gun is held by a valid, player-controlled Actor.
49
+ if not parent or not IsActor (parent ) then
50
+ self :Deactivate () -- If not held by an actor, deactivate.
51
+ return
52
+ end
53
+
54
+ local parentActor = ToActor (parent ) -- Cast to Actor base type.
55
+ -- Specific casting to AHuman or ACrab can be done if needed for type-specific logic.
56
+
57
+ if not parentActor :IsPlayerControlled () or parentActor .Status >= Actor .DYING then
58
+ self :Deactivate () -- Deactivate if not player controlled or if player is dying.
59
+ return
60
+ end
61
+
62
+ local controller = parentActor :GetController ()
63
+ if not controller then
64
+ self :Deactivate () -- Should not happen if IsPlayerControlled is true, but good check.
65
+ return
66
+ end
67
+
68
+ -- Deactivate if equipped in the background arm and a foreground item exists,
69
+ -- to allow the foreground item (e.g., another weapon) to be used.
70
+ if parentActor .EquippedBGItem and parentActor .EquippedBGItem .ID == self .ID and parentActor .EquippedItem then
71
+ self :Deactivate ()
72
+ -- Potentially return here if no further logic should run for a BG equipped grapple gun.
73
+ end
74
+
75
+ -- Magazine handling (visual representation of the hook's availability)
76
+ if self .Magazine and MovableMan :IsParticle (self .Magazine ) then
77
+ local magazineParticle = ToMOSParticle (self .Magazine )
78
+
79
+ -- Double tapping crouch retrieves the hook (if a grapple is active)
80
+ -- This logic seems to be for initiating a retrieve action from the gun itself.
81
+ -- The actual unhooking is handled by the Grapple.lua script's tap detection.
82
+ -- This section might be redundant if Grapple.lua's tap detection is comprehensive.
83
+ if magazineParticle .Scale == 1 then -- Assuming Scale 1 means hook is "loaded" / available to fire
84
+ -- The following stance offsets seem to be for when the hook is *not* fired yet.
85
+ -- Consider if this is the correct condition.
86
+ local parentSprite = ToMOSprite (self :GetParent ()) -- Assuming self:GetParent() is the gun's sprite component
87
+ if parentSprite then
88
+ local spriteWidth = parentSprite :GetSpriteWidth () or 0
89
+ self .StanceOffset = Vector (spriteWidth , 1 )
90
+ self .SharpStanceOffset = Vector (spriteWidth , 1 )
91
+ end
92
+
93
+ -- Crouch-tap logic (potentially for recalling an active hook)
94
+ if controller :IsState (Controller .BODY_PRONE ) then
95
+ if self .canTap then
96
+ controller :SetState (Controller .BODY_PRONE , false ) -- Prevent continuous prone state
97
+ self .tapTimerJump :Reset ()
98
+ -- self.didTap = true; -- Mark that a tap occurred (if used elsewhere)
99
+ self .canTap = false
100
+ self .tapCounter = self .tapCounter + 1
101
+ end
102
+ else
103
+ self .canTap = true -- Allow first tap when not prone
104
+ end
105
+
106
+ if self .tapTimerJump :IsPastSimMS (self .tapTime ) then
107
+ self .tapCounter = 0 -- Reset counter if too much time has passed
108
+ else
109
+ if self .tapCounter >= self .tapAmount then
110
+ -- If enough taps, activate the gun. This might be intended to fire/recall.
111
+ -- If a grapple is already out, Grapple.lua's tap detection should handle recall.
112
+ -- If no grapple is out, this would fire a new one.
113
+ -- Clarify the intent: is this to fire, or to send a signal to an existing grapple?
114
+ self :Activate () -- This will typically fire the HDFirearm.
115
+ self .tapCounter = 0
116
+ end
117
+ end
118
+ end
119
+
120
+ -- Guide arrow visibility logic
121
+ -- Show if magazine scale is 0 (hook is fired) AND not sharp aiming, OR if parent is moving fast.
122
+ local shouldShowGuide = false
123
+ if magazineParticle .Scale == 0 and not controller :IsState (Controller .AIM_SHARP ) then
124
+ shouldShowGuide = true
125
+ elseif parentActor .Vel and parentActor .Vel :MagnitudeIsGreaterThan (6 ) then
126
+ shouldShowGuide = true
127
+ end
128
+ self .guide = shouldShowGuide
129
+ else
130
+ self .guide = false -- No magazine or not a particle, so no guide based on it.
131
+ end
132
+
133
+ -- Draw the guide arrow if enabled and valid
134
+ if self .guide and self .arrow and self .arrow .ID ~= rte .NoMOID then
135
+ local frame = 0
136
+ if parentActor .Vel and parentActor .Vel :MagnitudeIsGreaterThan (12 ) then
137
+ frame = 1 -- Use a different arrow frame for higher speeds
138
+ end
139
+
140
+ -- Calculate positions for drawing the arrow
141
+ -- EyePos might not exist on all Actor types, ensure parentActor has it or use a fallback.
142
+ local eyePos = parentActor .EyePos or Vector (0 ,0 )
143
+ local startPos = (parentActor .Pos + eyePos + self .Pos )/ 3 -- Averaged position
144
+ local aimAngle = parentActor :GetAimAngle (true )
145
+ local aimDistance = parentActor .AimDistance or 50 -- Default AimDistance if not present
146
+ local guidePos = startPos + Vector (aimDistance + (parentActor .Vel and parentActor .Vel .Magnitude or 0 ), 0 ):RadRotate (aimAngle )
147
+
148
+ -- Ensure the arrow MO still exists before trying to draw with it
149
+ if MovableMan :IsValid (self .arrow ) then
150
+ PrimitiveMan :DrawBitmapPrimitive (ActivityMan :GetActivity ():ScreenOfPlayer (controller .Player ), guidePos , self .arrow , aimAngle , frame )
151
+ else
152
+ self .arrow = nil -- Arrow MO was deleted, nullify reference
153
+ end
154
+ end
155
+
156
+ -- Ensure magazine is visually "full" and ready if no grapple is active.
157
+ -- This assumes the HDFirearm's standard magazine logic handles firing.
158
+ -- If a grapple claw MO (the projectile) is active, Grapple.lua will hide the magazine.
159
+ -- This section ensures it's visible when no grapple is out.
160
+ if self .Magazine and MovableMan :IsParticle (self .Magazine ) then
161
+ local magParticle = ToMOSParticle (self .Magazine )
162
+ local isActiveGrapple = false
163
+ -- Check if there's an active grapple associated with this gun
164
+ for mo_instance in MovableMan :GetMOsByPreset (" Grapple Gun Claw" ) do
165
+ if mo_instance and mo_instance .parentGun and mo_instance .parentGun .ID == self .ID then
166
+ isActiveGrapple = true
167
+ break
168
+ end
169
+ end
170
+
171
+ if not isActiveGrapple then
172
+ magParticle .RoundCount = 1 -- Visually full
173
+ magParticle .Scale = 1 -- Visible
174
+ magParticle .Frame = 0 -- Standard frame
175
+ else
176
+ magParticle .Scale = 0 -- Hidden by active grapple (Grapple.lua also does this)
177
+ end
178
+ end
179
+ end
180
+
181
+ function Destroy (self )
182
+ -- Clean up the guide arrow if it exists
183
+ if self .arrow and self .arrow .ID ~= rte .NoMOID and MovableMan :IsValid (self .arrow ) then
184
+ MovableMan :RemoveMO (self .arrow )
185
+ self .arrow = nil
186
+ end
89
187
end
0 commit comments