diff -r bc7b6aa5d67a -r 7030706266df hedgewars/uGearsHandlersRope.pas --- a/hedgewars/uGearsHandlersRope.pas Sun Oct 28 15:18:26 2012 +0100 +++ b/hedgewars/uGearsHandlersRope.pas Fri Dec 06 22:20:53 2019 +0100 @@ -1,6 +1,6 @@ (* * Hedgewars, a free turn based strategy game - * Copyright (c) 2004-2012 Andrey Korotaev + * Copyright (c) 2004-2015 Andrey Korotaev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -13,7 +13,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *) {$INCLUDE "options.inc"} @@ -28,16 +28,46 @@ uses uConsts, uFloat, uCollisions, uVariables, uGearsList, uSound, uGearsUtils, uAmmos, uDebug, uUtils, uGearsHedgehog, uGearsRender; +const + IsNilHHFatal = false; + procedure doStepRopeAfterAttack(Gear: PGear); -var +var HHGear: PGear; + tX: hwFloat; begin HHGear := Gear^.Hedgehog^.Gear; + if HHGear = nil then + begin + OutError('ERROR: doStepRopeAfterAttack called while HHGear = nil', IsNilHHFatal); + DeleteGear(Gear); + exit() + end + else if not CurrentTeam^.ExtDriven and (FollowGear <> nil) then FollowGear := HHGear; + + tX:= HHGear^.X; + if WorldWrap(HHGear) and (WorldEdge = weWrap) and + ((TestCollisionXwithGear(HHGear, 1) <> 0) or (TestCollisionXwithGear(HHGear, -1) <> 0)) then + begin + HHGear^.X:= tX; + HHGear^.dX.isNegative:= hwRound(tX) > leftX + HHGear^.Radius * 2 + end; + + if (HHGear^.Hedgehog^.CurAmmoType = amParachute) and (HHGear^.dY > _0_39) then + begin + DeleteGear(Gear); + ApplyAmmoChanges(HHGear^.Hedgehog^); + HHGear^.Message:= HHGear^.Message or gmLJump; + exit + end; + if ((HHGear^.State and gstHHDriven) = 0) or (CheckGearDrowning(HHGear)) or (TestCollisionYwithGear(HHGear, 1) <> 0) then begin DeleteGear(Gear); + 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 + HHGear^.Hedgehog^.CurAmmoType:= amRope; isCursorVisible := false; ApplyAmmoChanges(HHGear^.Hedgehog^); exit @@ -45,7 +75,7 @@ HedgehogChAngle(HHGear); - if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then + if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) <> 0 then SetLittle(HHGear^.dX); if HHGear^.dY.isNegative and (TestCollisionYwithGear(HHGear, -1) <> 0) then @@ -53,7 +83,7 @@ HHGear^.X := HHGear^.X + HHGear^.dX; HHGear^.Y := HHGear^.Y + HHGear^.dY; HHGear^.dY := HHGear^.dY + cGravity; - + if (GameFlags and gfMoreWind) <> 0 then HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density; @@ -96,39 +126,58 @@ end; procedure doStepRopeWork(Gear: PGear); -var +var HHGear: PGear; len, tx, ty, nx, ny, ropeDx, ropeDy, mdX, mdY: hwFloat; lx, ly, cd: LongInt; haveCollision, haveDivided: boolean; - + wrongSide: boolean; begin - if GameTicks mod 4 <> 0 then exit; + HHGear := Gear^.Hedgehog^.Gear; + if HHGear = nil then + begin + OutError('ERROR: doStepRopeWork called while HHGear = nil', IsNilHHFatal); + DeleteGear(Gear); + exit() + end + else if not CurrentTeam^.ExtDriven and (FollowGear <> nil) then FollowGear := HHGear; - HHGear := Gear^.Hedgehog^.Gear; - - if ((HHGear^.State and gstHHDriven) = 0) - or (CheckGearDrowning(HHGear)) or (Gear^.PortalCounter <> 0) then + if ((HHGear^.State and gstHHDriven) = 0) or + (CheckGearDrowning(HHGear)) or (Gear^.PortalCounter <> 0) then begin PlaySound(sndRopeRelease); RopeDeleteMe(Gear, HHGear); exit end; + if GameTicks mod 4 <> 0 then exit; + + tX:= HHGear^.X; + if WorldWrap(HHGear) and (WorldEdge = weWrap) and + ((TestCollisionXwithGear(HHGear, 1) <> 0) or (TestCollisionXwithGear(HHGear, -1) <> 0)) then + begin + PlaySound(sndRopeRelease); + RopeDeleteMe(Gear, HHGear); + HHGear^.X:= tX; + HHGear^.dX.isNegative:= hwRound(tX) > leftX + HHGear^.Radius * 2; + exit + end; + + tX:= HHGear^.X; HHGear^.dX.QWordValue:= HHGear^.dX.QWordValue shl 2; HHGear^.dY.QWordValue:= HHGear^.dY.QWordValue shl 2; - if (Gear^.Message and gmLeft <> 0) and (not TestCollisionXwithGear(HHGear, -1)) then + if (Gear^.Message and gmLeft <> 0) and (TestCollisionXwithGear(HHGear, -1) = 0) then HHGear^.dX := HHGear^.dX - _0_0032; - if (Gear^.Message and gmRight <> 0) and (not TestCollisionXwithGear(HHGear, 1)) then + if (Gear^.Message and gmRight <> 0) and (TestCollisionXwithGear(HHGear, 1) = 0) then HHGear^.dX := HHGear^.dX + _0_0032; // vector between hedgehog and rope attaching point ropeDx := HHGear^.X - Gear^.X; ropeDy := HHGear^.Y - Gear^.Y; - if TestCollisionYwithGear(HHGear, 1) = 0 then + if TestCollisionYwithXYShift(HHGear, 0, 1, 1) = 0 then begin // depending on the rope vector we know which X-side to check for collision @@ -139,12 +188,12 @@ cd:= 1; // apply gravity if there is no obstacle - if not TestCollisionXwithGear(HHGear, cd) then + if TestCollisionXwithXYShift(HHGear, _2*cd, 0, cd, true) = 0 then HHGear^.dY := HHGear^.dY + cGravity * 16; if (GameFlags and gfMoreWind) <> 0 then // apply wind if there's no obstacle - if not TestCollisionXwithGear(HHGear, hwSign(cWindSpeed)) then + if TestCollisionXwithGear(HHGear, hwSign(cWindSpeed)) = 0 then HHGear^.dX := HHGear^.dX + cWindSpeed * 16 / HHGear^.Density; end; @@ -164,13 +213,13 @@ ty := HHGear^.Y; if ((Gear^.Message and gmDown) <> 0) and (Gear^.Elasticity < Gear^.Friction) then - if not (TestCollisionXwithGear(HHGear, hwSign(ropeDx)) - or (TestCollisionYwithGear(HHGear, hwSign(ropeDy)) <> 0)) then + if not ((TestCollisionXwithXYShift(HHGear, _2*hwSign(ropeDx), 0, hwSign(ropeDx), true) <> 0) + or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, hwSign(ropeDy), hwSign(ropeDy)) <> 0))) then Gear^.Elasticity := Gear^.Elasticity + _1_2; if ((Gear^.Message and gmUp) <> 0) and (Gear^.Elasticity > _30) then - if not (TestCollisionXwithGear(HHGear, -hwSign(ropeDx)) - or (TestCollisionYwithGear(HHGear, -hwSign(ropeDy)) <> 0)) then + if not ((TestCollisionXwithXYShift(HHGear, -_2*hwSign(ropeDx), 0, -hwSign(ropeDx), true) <> 0) + or ((ropeDy.QWordValue <> 0) and (TestCollisionYwithXYShift(HHGear, 0, -hwSign(ropeDy), -hwSign(ropeDy)) <> 0))) then Gear^.Elasticity := Gear^.Elasticity - _1_2; HHGear^.X := Gear^.X + mdX * Gear^.Elasticity; @@ -178,8 +227,6 @@ HHGear^.dX := HHGear^.X - tx; HHGear^.dY := HHGear^.Y - ty; - //// - haveDivided := false; // check whether rope needs dividing @@ -194,12 +241,12 @@ begin lx := hwRound(nx); ly := hwRound(ny); - if ((ly and LAND_HEIGHT_MASK) = 0) and ((lx and LAND_WIDTH_MASK) = 0) and ((Land[ly, lx] and $FF00) <> 0) then + if ((ly and LAND_HEIGHT_MASK) = 0) and ((lx and LAND_WIDTH_MASK) = 0) and (Land[ly, lx] > lfAllObjMask) then begin - ny := _1 / Distance(ropeDx, ropeDy); + tx := _1 / Distance(ropeDx, ropeDy); // old rope pos - nx := ropeDx * ny; - ny := ropeDy * ny; + nx := ropeDx * tx; + ny := ropeDy * tx; with RopePoints.ar[RopePoints.Count] do begin @@ -208,9 +255,12 @@ if RopePoints.Count = 0 then RopePoints.HookAngle := DxDy2Angle(Gear^.dY, Gear^.dX); b := (nx * HHGear^.dY) > (ny * HHGear^.dX); + sx:= Gear^.dX.isNegative; + sy:= Gear^.dY.isNegative; + sb:= Gear^.dX.QWordValue < Gear^.dY.QWordValue; dLen := len end; - + with RopePoints.rounded[RopePoints.Count] do begin X := hwRound(Gear^.X); @@ -220,7 +270,7 @@ Gear^.X := Gear^.X + nx * len; Gear^.Y := Gear^.Y + ny * len; inc(RopePoints.Count); - TryDo(RopePoints.Count <= MAXROPEPOINTS, 'Rope points overflow', true); + if checkFails(RopePoints.Count <= MAXROPEPOINTS, 'Rope points overflow', true) then exit; Gear^.Elasticity := Gear^.Elasticity - len; Gear^.Friction := Gear^.Friction - len; haveDivided := true; @@ -240,31 +290,55 @@ ty := RopePoints.ar[Pred(RopePoints.Count)].Y; mdX := tx - Gear^.X; mdY := ty - Gear^.Y; - if RopePoints.ar[Pred(RopePoints.Count)].b xor (mdX * (ty - HHGear^.Y) > (tx - HHGear^.X) * mdY) then + ropeDx:= tx - HHGear^.X; + ropeDy:= ty - HHGear^.Y; + if RopePoints.ar[Pred(RopePoints.Count)].b xor (mdX * ropeDy > ropeDx * mdY) then begin dec(RopePoints.Count); - Gear^.X := RopePoints.ar[RopePoints.Count].X; - Gear^.Y := RopePoints.ar[RopePoints.Count].Y; - Gear^.Elasticity := Gear^.Elasticity + RopePoints.ar[RopePoints.Count].dLen; - Gear^.Friction := Gear^.Friction + RopePoints.ar[RopePoints.Count].dLen; + Gear^.X := tx; + Gear^.Y := ty; + + // oops, opposite quadrant, don't restore hog position in such case, just remove the point + wrongSide:= (ropeDx.isNegative = RopePoints.ar[RopePoints.Count].sx) + and (ropeDy.isNegative = RopePoints.ar[RopePoints.Count].sy); + + // previous check could be inaccurate in vertical/horizontal rope positions, + // so perform this check also, even though odds are 1 to 415927 to hit this + if (not wrongSide) + and ((ropeDx.isNegative = RopePoints.ar[RopePoints.Count].sx) + <> (ropeDy.isNegative = RopePoints.ar[RopePoints.Count].sy)) then + if RopePoints.ar[RopePoints.Count].sb then + wrongSide:= ropeDy.isNegative = RopePoints.ar[RopePoints.Count].sy + else + wrongSide:= ropeDx.isNegative = RopePoints.ar[RopePoints.Count].sx; - // restore hog position - len := _1 / Distance(mdX, mdY); - mdX := mdX * len; - mdY := mdY * len; + if wrongSide then + begin + Gear^.Elasticity := Gear^.Elasticity - RopePoints.ar[RopePoints.Count].dLen; + Gear^.Friction := Gear^.Friction - RopePoints.ar[RopePoints.Count].dLen; + end else + begin + Gear^.Elasticity := Gear^.Elasticity + RopePoints.ar[RopePoints.Count].dLen; + Gear^.Friction := Gear^.Friction + RopePoints.ar[RopePoints.Count].dLen; - HHGear^.X := Gear^.X - mdX * Gear^.Elasticity; - HHGear^.Y := Gear^.Y - mdY * Gear^.Elasticity; + // restore hog position + len := _1 / Distance(mdX, mdY); + mdX := mdX * len; + mdY := mdY * len; + + HHGear^.X := Gear^.X - mdX * Gear^.Elasticity; + HHGear^.Y := Gear^.Y - mdY * Gear^.Elasticity; + end; end end; haveCollision := false; - if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then + if TestCollisionXwithXYShift(HHGear, _2*hwSign(HHGear^.dX), 0, hwSign(HHGear^.dX), true) <> 0 then begin HHGear^.dX := -_0_6 * HHGear^.dX; haveCollision := true end; - if TestCollisionYwithGear(HHGear, hwSign(HHGear^.dY)) <> 0 then + if TestCollisionYwithXYShift(HHGear, 0, 1*hwSign(HHGear^.dY), hwSign(HHGear^.dY)) <> 0 then begin HHGear^.dY := -_0_6 * HHGear^.dY; haveCollision := true @@ -343,13 +417,13 @@ if (Gear^.State and gstAttacked) = 0 then begin OnUsedAmmo(HHGear^.Hedgehog^); - Gear^.State := Gear^.State or gstAttacked + Gear^.State := Gear^.State or gstAttacked; + ApplyAmmoChanges(HHGear^.Hedgehog^); end; - ApplyAmmoChanges(HHGear^.Hedgehog^) end; procedure doStepRopeAttach(Gear: PGear); -var +var HHGear: PGear; tx, ty, tt: hwFloat; begin @@ -358,44 +432,55 @@ Gear^.Elasticity := Gear^.Elasticity + _1; HHGear := Gear^.Hedgehog^.Gear; + if HHGear = nil then + begin + OutError('ERROR: doStepRopeAttach called while HHGear = nil', IsNilHHFatal); + DeleteGear(Gear); + exit() + end + else if not CurrentTeam^.ExtDriven and (FollowGear <> nil) then FollowGear := HHGear; + + // Destroy rope if it touched bouncy or world wrap world edge. + // TODO: Allow to shoot rope through the world wrap edge and rope normally. + if (WorldWrap(Gear) and (WorldEdge = weWrap)) or + ((WorldEdge = weBounce) and ((hwRound(Gear^.X) <= LeftX) or (hwRound(Gear^.X) >= RightX))) then + begin + HHGear^.State := HHGear^.State and (not (gstAttacking or gstHHJumping or gstHHHJump)); + HHGear^.Message := HHGear^.Message and (not gmAttack); + DeleteGear(Gear); + if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then + HHGear^.Hedgehog^.CurAmmoType:= amRope; + isCursorVisible := false; + ApplyAmmoChanges(HHGear^.Hedgehog^); + exit() + end; + DeleteCI(HHGear); if (HHGear^.State and gstMoving) <> 0 then begin - if TestCollisionXwithGear(HHGear, hwSign(HHGear^.dX)) then - SetLittle(HHGear^.dX); - if HHGear^.dY.isNegative and (TestCollisionYwithGear(HHGear, -1) <> 0) then - HHGear^.dY := _0; - - HHGear^.X := HHGear^.X + HHGear^.dX; + doStepHedgehogMoving(HHGear); Gear^.X := Gear^.X + HHGear^.dX; + Gear^.Y := Gear^.Y + HHGear^.dY; - if TestCollisionYwithGear(HHGear, 1) <> 0 then - begin - CheckHHDamage(HHGear); - HHGear^.dY := _0 - //HHGear^.State:= HHGear^.State and (not (gstHHJumping or gstHHHJump)); - end - else - begin - HHGear^.Y := HHGear^.Y + HHGear^.dY; - Gear^.Y := Gear^.Y + HHGear^.dY; - HHGear^.dY := HHGear^.dY + cGravity; - if (GameFlags and gfMoreWind) <> 0 then - HHGear^.dX := HHGear^.dX + cWindSpeed / HHGear^.Density - end; + // hedgehog can teleport up to 5 pixels upwards when sliding, + // so we have to give up the maintained rope length + // after doStepHedgehogMoving() call and recalculate + // it based on the gear and current hedgehog positions + Gear^.Elasticity:= int2hwFloat(hwRound(Distance(Gear^.X - HHGear^.X, Gear^.Y - HHGear^.Y) + _0_001)); tt := Gear^.Elasticity; tx := _0; ty := _0; while tt > _20 do begin - 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)] and $FF00) <> 0) then + 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 begin Gear^.X := Gear^.X + tx; Gear^.Y := Gear^.Y + ty; Gear^.Elasticity := tt; Gear^.doStep := @doStepRopeWork; + PlaySound(sndRopeAttach); with HHGear^ do begin @@ -404,8 +489,6 @@ end; RopeRemoveFromAmmo(Gear, HHGear); - - tt := _0; exit end; tx := tx + Gear^.dX + Gear^.dX; @@ -414,8 +497,8 @@ end; end; - if Gear^.Elasticity < _20 then Gear^.CollisionMask:= $FF00 - else Gear^.CollisionMask:= $FF7F; + if Gear^.Elasticity < _20 then Gear^.CollisionMask:= lfLandMask + else Gear^.CollisionMask:= lfNotCurHogCrate; //lfNotObjMask or lfNotHHObjMask; CheckCollision(Gear); if (Gear^.State and gstCollision) <> 0 then @@ -447,6 +530,10 @@ Message := Message and (not gmAttack) end; DeleteGear(Gear); + if (GetAmmoEntry(HHGear^.Hedgehog^, amRope)^.Count >= 1) and ((Ammoz[HHGear^.Hedgehog^.CurAmmoType].Ammo.Propz and ammoprop_AltUse) <> 0) and (HHGear^.Hedgehog^.MultiShootAttacks = 0) then + HHGear^.Hedgehog^.CurAmmoType:= amRope; + isCursorVisible := false; + ApplyAmmoChanges(HHGear^.Hedgehog^); exit; end; if CheckGearDrowning(HHGear) then DeleteGear(Gear)