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