1 (*
2  * Hedgewars, a free turn based strategy game
3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *)
18 
19 {$INCLUDE "options.inc"}
20 unit uGearsHandlersRope;
21 interface
22 
23 uses uTypes;
24 
25 procedure doStepRope(Gear: PGear);
26 
27 implementation
28 uses uConsts, uFloat, uCollisions, uVariables, uGearsList, uSound, uGearsUtils,
29     uAmmos, uDebug, uUtils, uGearsHedgehog, uGearsRender;
30 
31 const
32     IsNilHHFatal = false;
33 
34 procedure doStepRopeAfterAttack(Gear: PGear);
35 var
36     HHGear: PGear;
37     tX:     hwFloat;
38 begin
39     HHGear := Gear^.Hedgehog^.Gear;
40     if HHGear = nil then
41         begin
42         OutError('ERROR: doStepRopeAfterAttack called while HHGear = nil', IsNilHHFatal);
43         DeleteGear(Gear);
44         exit()
45         end
46     else if not CurrentTeam^.ExtDriven and (FollowGear <> nil) then FollowGear := HHGear;
47 
48     tX:= HHGear^.X;
49     if WorldWrap(HHGear) and (WorldEdge = weWrap) and
50        ((TestCollisionXwithGear(HHGear, 1) <> 0) or (TestCollisionXwithGear(HHGear, -1) <> 0))  then
51         begin
52         HHGear^.X:= tX;
53         HHGear^.dX.isNegative:= hwRound(tX) > leftX + HHGear^.Radius * 2
54         end;
55 
56     if (HHGear^.Hedgehog^.CurAmmoType = amParachute) and (HHGear^.dY > _0_39) then
57         begin
58         DeleteGear(Gear);
59         ApplyAmmoChanges(HHGear^.Hedgehog^);
60         HHGear^.Message:= HHGear^.Message or gmLJump;
61         exit
62         end;
63 
64     if ((HHGear^.State and gstHHDriven) = 0)
65     or (CheckGearDrowning(HHGear))
66     or (TestCollisionYwithGear(HHGear, 1) <> 0) then
67         begin
68         DeleteGear(Gear);
69         if (TestCollisionYwithGear(HHGear, 1) <> 0) and (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
70             HHGear^.Hedgehog^.CurAmmoType:= amRope;
71         isCursorVisible := false;
72         ApplyAmmoChanges(HHGear^.Hedgehog^);
73         exit
74         end;
75 
76     HedgehogChAngle(HHGear);
77 
78     if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) <> 0 then
79         SetLittle(HHGear^.dX);
80 
81     if HHGear^.dY.isNegative and (TestCollisionYwithGear(HHGear, -1) <> 0) then
82         HHGear^.dY := _0;
83     HHGear^.X := HHGear^.X + HHGear^.dX;
84     HHGear^.Y := HHGear^.Y + HHGear^.dY;
85     HHGear^.dY := HHGear^.dY + cGravity;
86 
87     if (GameFlags and gfMoreWind) <> 0 then
88         HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density;
89 
90     if (Gear^.Message and gmAttack) <> 0 then
91         begin
92         Gear^.X := HHGear^.X;
93         Gear^.Y := HHGear^.Y;
94 
95         ApplyAngleBounds(Gear^.Hedgehog^, amRope);
96 
97         Gear^.dX := SignAs(AngleSin(HHGear^.Angle), HHGear^.dX);
98         Gear^.dY := -AngleCos(HHGear^.Angle);
99         Gear^.Friction := _4_5 * cRopePercent;
100         Gear^.Elasticity := _0;
101         Gear^.State := Gear^.State and (not gsttmpflag);
102         Gear^.doStep := @doStepRope;
103         end
104 end;
105 
106 procedure RopeDeleteMe(Gear, HHGear: PGear);
107 begin
108     with HHGear^ do
109         begin
110         Message := Message and (not gmAttack);
111         State := (State or gstMoving) and (not gstWinner);
112         end;
113     DeleteGear(Gear)
114 end;
115 
116 procedure RopeWaitCollision(Gear, HHGear: PGear);
117 begin
118     with HHGear^ do
119         begin
120         Message := Message and (not gmAttack);
121         State := State or gstMoving;
122         end;
123     RopePoints.Count := 0;
124     Gear^.Elasticity := _0;
125     Gear^.doStep := @doStepRopeAfterAttack
126 end;
127 
128 procedure doStepRopeWork(Gear: PGear);
129 var
130     HHGear: PGear;
131     len, tx, ty, nx, ny, ropeDx, ropeDy, mdX, mdY: hwFloat;
132     lx, ly, cd: LongInt;
133     haveCollision,
134     haveDivided: boolean;
135     wrongSide: boolean;
136 begin
137     HHGear := Gear^.Hedgehog^.Gear;
138     if HHGear = nil then
139         begin
140         OutError('ERROR: doStepRopeWork called while HHGear = nil', IsNilHHFatal);
141         DeleteGear(Gear);
142         exit()
143         end
144     else if not CurrentTeam^.ExtDriven and (FollowGear <> nil) then FollowGear := HHGear;
145 
146     if ((HHGear^.State and gstHHDriven) = 0) or
147         (CheckGearDrowning(HHGear)) or (Gear^.PortalCounter <> 0) then
148         begin
149         PlaySound(sndRopeRelease);
150         RopeDeleteMe(Gear, HHGear);
151         exit
152         end;
153 
154     if GameTicks mod 4 <> 0 then exit;
155 
156     tX:= HHGear^.X;
157     if WorldWrap(HHGear) and (WorldEdge = weWrap) and
158        ((TestCollisionXwithGear(HHGear, 1) <> 0) or (TestCollisionXwithGear(HHGear, -1) <> 0))  then
159         begin
160         PlaySound(sndRopeRelease);
161         RopeDeleteMe(Gear, HHGear);
162         HHGear^.X:= tX;
163         HHGear^.dX.isNegative:= hwRound(tX) > leftX + HHGear^.Radius * 2;
164         exit
165         end;
166 
167     tX:= HHGear^.X;
168     HHGear^.dX.QWordValue:= HHGear^.dX.QWordValue shl 2;
169     HHGear^.dY.QWordValue:= HHGear^.dY.QWordValue shl 2;
170     if (Gear^.Message and gmLeft  <> 0) and (TestCollisionXwithGear(HHGear, -1) = 0) then
171         HHGear^.dX := HHGear^.dX - _0_0032;
172 
173     if (Gear^.Message and gmRight <> 0) and (TestCollisionXwithGear(HHGear,  1) = 0) then
174         HHGear^.dX := HHGear^.dX + _0_0032;
175 
176     // vector between hedgehog and rope attaching point
177     ropeDx := HHGear^.X - Gear^.X;
178     ropeDy := HHGear^.Y - Gear^.Y;
179 
180     if TestCollisionYwithXYShift(HHGear, 0, 1, 1) = 0 then
181         begin
182 
183         // depending on the rope vector we know which X-side to check for collision
184         // in order to find out if the hog can still be moved by gravity
185         if ropeDx.isNegative = RopeDy.IsNegative then
186             cd:= -1
187         else
188             cd:= 1;
189 
190         // apply gravity if there is no obstacle
191         if TestCollisionXwithXYShift(HHGear, _2*cd, 0, cd, true) = 0 then
192             HHGear^.dY := HHGear^.dY + cGravity * 16;
193 
194         if (GameFlags and gfMoreWind) <> 0 then
195             // apply wind if there's no obstacle
196             if TestCollisionXwithGear(HHGear, hwSign(cWindSpeed)) = 0 then
197                 HHGear^.dX := HHGear^.dX + cWindSpeed * 16 / HHGear^.Density;
198         end;
199 
200     mdX := ropeDx + HHGear^.dX;
201     mdY := ropeDy + HHGear^.dY;
202     len := _1 / Distance(mdX, mdY);
203     // rope vector plus hedgehog direction vector normalized
204     mdX := mdX * len;
205     mdY := mdY * len;
206 
207     // for visual purposes only
208     Gear^.dX := mdX;
209     Gear^.dY := mdY;
210 
211     /////
212     tx := HHGear^.X;
213     ty := HHGear^.Y;
214 
215     if ((Gear^.Message and gmDown) <> 0) and (Gear^.Elasticity < Gear^.Friction) then
216         if not ((TestCollisionXwithXYShift(HHGear, _2*hwSign(ropeDx), 0, hwSign(ropeDx), true) <> 0)
217         or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, hwSign(ropeDy), hwSign(ropeDy)) <> 0))) then
218             Gear^.Elasticity := Gear^.Elasticity + _1_2;
219 
220     if ((Gear^.Message and gmUp) <> 0) and (Gear^.Elasticity > _30) then
221         if not ((TestCollisionXwithXYShift(HHGear, -_2*hwSign(ropeDx), 0, -hwSign(ropeDx), true) <> 0)
222         or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, -hwSign(ropeDy), -hwSign(ropeDy)) <> 0))) then
223             Gear^.Elasticity := Gear^.Elasticity - _1_2;
224 
225     HHGear^.X := Gear^.X + mdX * Gear^.Elasticity;
226     HHGear^.Y := Gear^.Y + mdY * Gear^.Elasticity;
227 
228     HHGear^.dX := HHGear^.X - tx;
229     HHGear^.dY := HHGear^.Y - ty;
230 
231     haveDivided := false;
232     // check whether rope needs dividing
233 
234     len := Gear^.Elasticity - _5;
235     nx := Gear^.X + mdX * len;
236     ny := Gear^.Y + mdY * len;
237     tx := mdX * _1_2; // should be the same as increase step
238     ty := mdY * _1_2;
239 
240     while len > _3 do
241         begin
242         lx := hwRound(nx);
243         ly := hwRound(ny);
244         if ((ly and LAND_HEIGHT_MASK) = 0) and ((lx and LAND_WIDTH_MASK) = 0) and (Land[ly, lx] > lfAllObjMask) then
245             begin
246             tx := _1 / Distance(ropeDx, ropeDy);
247             // old rope pos
248             nx := ropeDx * tx;
249             ny := ropeDy * tx;
250 
251             with RopePoints.ar[RopePoints.Count] do
252                 begin
253                 X := Gear^.X;
254                 Y := Gear^.Y;
255                 if RopePoints.Count = 0 then
256                     RopePoints.HookAngle := DxDy2Angle(Gear^.dY, Gear^.dX);
257                 b := (nx * HHGear^.dY) > (ny * HHGear^.dX);
258                 sx:= Gear^.dX.isNegative;
259                 sy:= Gear^.dY.isNegative;
260                 sb:= Gear^.dX.QWordValue < Gear^.dY.QWordValue;
261                 dLen := len
262                 end;
263 
264             with RopePoints.rounded[RopePoints.Count] do
265                 begin
266                 X := hwRound(Gear^.X);
267                 Y := hwRound(Gear^.Y);
268                 end;
269 
270             Gear^.X := Gear^.X + nx * len;
271             Gear^.Y := Gear^.Y + ny * len;
272             inc(RopePoints.Count);
273             if checkFails(RopePoints.Count <= MAXROPEPOINTS, 'Rope points overflow', true) then exit;
274             Gear^.Elasticity := Gear^.Elasticity - len;
275             Gear^.Friction := Gear^.Friction - len;
276             haveDivided := true;
277             break
278             end;
279         nx := nx - tx;
280         ny := ny - ty;
281 
282         // len := len - _1_2 // should be the same as increase step
283         len.QWordValue := len.QWordValue - _1_2.QWordValue;
284         end;
285 
286     if not haveDivided then
287         if RopePoints.Count > 0 then // check whether the last dividing point could be removed
288             begin
289             tx := RopePoints.ar[Pred(RopePoints.Count)].X;
290             ty := RopePoints.ar[Pred(RopePoints.Count)].Y;
291             mdX := tx - Gear^.X;
292             mdY := ty - Gear^.Y;
293             ropeDx:= tx - HHGear^.X;
294             ropeDy:= ty - HHGear^.Y;
295             if RopePoints.ar[Pred(RopePoints.Count)].b xor (mdX * ropeDy > ropeDx * mdY) then
296                 begin
297                 dec(RopePoints.Count);
298                 Gear^.X := tx;
299                 Gear^.Y := ty;
300 
301                 // oops, opposite quadrant, don't restore hog position in such case, just remove the point
302                 wrongSide:= (ropeDx.isNegative = RopePoints.ar[RopePoints.Count].sx)
303                     and (ropeDy.isNegative = RopePoints.ar[RopePoints.Count].sy);
304 
305                 // previous check could be inaccurate in vertical/horizontal rope positions,
306                 // so perform this check also, even though odds are 1 to 415927 to hit this
307                 if (not wrongSide)
308                     and ((ropeDx.isNegative = RopePoints.ar[RopePoints.Count].sx)
309                       <> (ropeDy.isNegative = RopePoints.ar[RopePoints.Count].sy)) then
310                     if RopePoints.ar[RopePoints.Count].sb then
311                         wrongSide:= ropeDy.isNegative = RopePoints.ar[RopePoints.Count].sy
312                         else
313                         wrongSide:= ropeDx.isNegative = RopePoints.ar[RopePoints.Count].sx;
314 
315                 if wrongSide then
316                     begin
317                     Gear^.Elasticity := Gear^.Elasticity - RopePoints.ar[RopePoints.Count].dLen;
318                     Gear^.Friction := Gear^.Friction - RopePoints.ar[RopePoints.Count].dLen;
319                     end else
320                     begin
321                     Gear^.Elasticity := Gear^.Elasticity + RopePoints.ar[RopePoints.Count].dLen;
322                     Gear^.Friction := Gear^.Friction + RopePoints.ar[RopePoints.Count].dLen;
323 
324                     // restore hog position
325                     len := _1 / Distance(mdX, mdY);
326                     mdX := mdX * len;
327                     mdY := mdY * len;
328 
329                     HHGear^.X := Gear^.X - mdX * Gear^.Elasticity;
330                     HHGear^.Y := Gear^.Y - mdY * Gear^.Elasticity;
331                     end;
332                 end
333             end;
334 
335     haveCollision := false;
336     if TestCollisionXwithXYShift(HHGear, _2*hwSign(HHGear^.dX), 0, hwSign(HHGear^.dX), true) <> 0 then
337         begin
338         HHGear^.dX := -_0_6 * HHGear^.dX;
339         haveCollision := true
340         end;
341     if TestCollisionYwithXYShift(HHGear, 0, 1*hwSign(HHGear^.dY), hwSign(HHGear^.dY)) <> 0 then
342         begin
343         HHGear^.dY := -_0_6 * HHGear^.dY;
344         haveCollision := true
345         end;
346 
347     if haveCollision and (Gear^.Message and (gmLeft or gmRight) <> 0) and (Gear^.Message and (gmUp or gmDown) <> 0) then
348         begin
349         HHGear^.dX := SignAs(hwAbs(HHGear^.dX) + _0_8, HHGear^.dX);
350         HHGear^.dY := SignAs(hwAbs(HHGear^.dY) + _0_8, HHGear^.dY)
351         end;
352 
353     len := hwSqr(HHGear^.dX) + hwSqr(HHGear^.dY);
354     if len > _10 then
355         begin
356         len := _3_2 / hwSqrt(len);
357         HHGear^.dX := HHGear^.dX * len;
358         HHGear^.dY := HHGear^.dY * len;
359         end;
360 
361     haveCollision:= ((hwRound(Gear^.Y) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X) and LAND_WIDTH_MASK) = 0) and ((Land[hwRound(Gear^.Y), hwRound(Gear^.X)]) <> 0);
362 
363     if not haveCollision then
364         begin
365         // backup gear location
366         tx:= Gear^.X;
367         ty:= Gear^.Y;
368 
369         if RopePoints.Count > 0 then
370             begin
371             // set gear location to the remote end of the rope, the attachment point
372             Gear^.X:= RopePoints.ar[0].X;
373             Gear^.Y:= RopePoints.ar[0].Y;
374             end;
375 
376         CheckCollision(Gear);
377         // if we haven't found any collision yet then check the other side too
378         if (Gear^.State and gstCollision) = 0 then
379             begin
380             Gear^.dX.isNegative:= not Gear^.dX.isNegative;
381             Gear^.dY.isNegative:= not Gear^.dY.isNegative;
382             CheckCollision(Gear);
383             Gear^.dX.isNegative:= not Gear^.dX.isNegative;
384             Gear^.dY.isNegative:= not Gear^.dY.isNegative;
385             end;
386 
387         haveCollision:= (Gear^.State and gstCollision) <> 0;
388 
389         // restore gear location
390         Gear^.X:= tx;
391         Gear^.Y:= ty;
392         end;
393 
394     // if the attack key is pressed, lose rope contact as well
395     if (Gear^.Message and gmAttack) <> 0 then
396         haveCollision:= false;
397 
398     HHGear^.dX.QWordValue:= HHGear^.dX.QWordValue shr 2;
399     HHGear^.dY.QWordValue:= HHGear^.dY.QWordValue shr 2;
400     if (not haveCollision) and ((Gear^.State and gsttmpFlag) <> 0) then
401         begin
402             begin
403             PlaySound(sndRopeRelease);
404             if Gear^.Hedgehog^.CurAmmoType <> amParachute then
405                 RopeWaitCollision(Gear, HHGear)
406             else
407                 RopeDeleteMe(Gear, HHGear)
408             end
409         end
410     else
411         if (Gear^.State and gsttmpFlag) = 0 then
412             Gear^.State := Gear^.State or gsttmpFlag;
413 end;
414 
415 procedure RopeRemoveFromAmmo(Gear, HHGear: PGear);
416 begin
417     if (Gear^.State and gstAttacked) = 0 then
418         begin
419         OnUsedAmmo(HHGear^.Hedgehog^);
420         Gear^.State := Gear^.State or gstAttacked;
421         ApplyAmmoChanges(HHGear^.Hedgehog^);
422         end;
423 end;
424 
425 procedure doStepRopeAttach(Gear: PGear);
426 var
427     HHGear: PGear;
428     tx, ty, tt: hwFloat;
429 begin
430     Gear^.X := Gear^.X - Gear^.dX;
431     Gear^.Y := Gear^.Y - Gear^.dY;
432     Gear^.Elasticity := Gear^.Elasticity + _1;
433 
434     HHGear := Gear^.Hedgehog^.Gear;
435     if HHGear = nil then
436         begin
437         OutError('ERROR: doStepRopeAttach called while HHGear = nil', IsNilHHFatal);
438         DeleteGear(Gear);
439         exit()
440         end
441     else if not CurrentTeam^.ExtDriven and (FollowGear <> nil) then FollowGear := HHGear;
442 
443     // Destroy rope if it touched bouncy or world wrap world edge.
444     // TODO: Allow to shoot rope through the world wrap edge and rope normally.
445     if (WorldWrap(Gear) and (WorldEdge = weWrap)) or
446        ((WorldEdge = weBounce) and ((hwRound(Gear^.X) <= LeftX) or (hwRound(Gear^.X) >= RightX))) then
447         begin
448         HHGear^.State := HHGear^.State and (not (gstAttacking or gstHHJumping or gstHHHJump));
449         HHGear^.Message := HHGear^.Message and (not gmAttack);
450         DeleteGear(Gear);
451         if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
452             HHGear^.Hedgehog^.CurAmmoType:= amRope;
453         isCursorVisible := false;
454         ApplyAmmoChanges(HHGear^.Hedgehog^);
455         exit()
456         end;
457 
458     DeleteCI(HHGear);
459 
460     if (HHGear^.State and gstMoving) <> 0 then
461         begin
462         doStepHedgehogMoving(HHGear);
463         Gear^.X := Gear^.X + HHGear^.dX;
464         Gear^.Y := Gear^.Y + HHGear^.dY;
465 
466         // hedgehog can teleport up to 5 pixels upwards when sliding,
467         // so we have to give up the maintained rope length
468         // after doStepHedgehogMoving() call and recalculate
469         // it based on the gear and current hedgehog positions
470         Gear^.Elasticity:= int2hwFloat(hwRound(Distance(Gear^.X - HHGear^.X, Gear^.Y - HHGear^.Y) + _0_001));
471 
472         tt := Gear^.Elasticity;
473         tx := _0;
474         ty := _0;
475         while tt > _20 do
476             begin
477             if ((hwRound(Gear^.Y+ty) and LAND_HEIGHT_MASK) = 0) and ((hwRound(Gear^.X+tx) and LAND_WIDTH_MASK) = 0) and (Land[hwRound(Gear^.Y+ty), hwRound(Gear^.X+tx)] > lfAllObjMask) then
478                 begin
479                 Gear^.X := Gear^.X + tx;
480                 Gear^.Y := Gear^.Y + ty;
481                 Gear^.Elasticity := tt;
482                 Gear^.doStep := @doStepRopeWork;
483 
484                 PlaySound(sndRopeAttach);
485                 with HHGear^ do
486                     begin
487                     State := State and (not (gstAttacking or gstHHJumping or gstHHHJump));
488                     Message := Message and (not gmAttack)
489                     end;
490 
491                 RopeRemoveFromAmmo(Gear, HHGear);
492                 exit
493                 end;
494             tx := tx + Gear^.dX + Gear^.dX;
495             ty := ty + Gear^.dY + Gear^.dY;
496             tt := tt - _2;
497             end;
498         end;
499 
500     if Gear^.Elasticity < _20 then Gear^.CollisionMask:= lfLandMask
501     else Gear^.CollisionMask:= lfNotCurHogCrate; //lfNotObjMask or lfNotHHObjMask;
502     CheckCollision(Gear);
503 
504     if (Gear^.State and gstCollision) <> 0 then
505         if Gear^.Elasticity < _10 then
506             Gear^.Elasticity := _10000
507     else
508         begin
509         Gear^.doStep := @doStepRopeWork;
510         PlaySound(sndRopeAttach);
511         with HHGear^ do
512             begin
513             State := State and (not (gstAttacking or gstHHJumping or gstHHHJump));
514             Message := Message and (not gmAttack)
515             end;
516 
517         RopeRemoveFromAmmo(Gear, HHGear);
518 
519         exit
520         end;
521 
522     if (Gear^.Elasticity > Gear^.Friction)
523         or ((Gear^.Message and gmAttack) = 0)
524         or ((HHGear^.State and gstHHDriven) = 0)
525         or (HHGear^.Damage > 0) then
526             begin
527             with Gear^.Hedgehog^.Gear^ do
528                 begin
529                 State := State and (not gstAttacking);
530                 Message := Message and (not gmAttack)
531                 end;
532         DeleteGear(Gear);
533         if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then
534             HHGear^.Hedgehog^.CurAmmoType:= amRope;
535         isCursorVisible := false;
536         ApplyAmmoChanges(HHGear^.Hedgehog^);
537         exit;
538         end;
539     if CheckGearDrowning(HHGear) then DeleteGear(Gear)
540 end;
541 
542 procedure doStepRope(Gear: PGear);
543 begin
544     Gear^.dX := - Gear^.dX;
545     Gear^.dY := - Gear^.dY;
546     Gear^.doStep := @doStepRopeAttach;
547     PlaySound(sndRopeShot)
548 end;
549 
550 end.
551