1//************************************************************************** 2//** 3//** ## ## ## ## ## #### #### ### ### 4//** ## ## ## ## ## ## ## ## ## ## #### #### 5//** ## ## ## ## ## ## ## ## ## ## ## ## ## ## 6//** ## ## ######## ## ## ## ## ## ## ## ### ## 7//** ### ## ## ### ## ## ## ## ## ## 8//** # ## ## # #### #### ## ## 9//** 10//** $Id: EntityEx.Physics.vc 4356 2010-12-24 03:49:32Z firebrand_kh $ 11//** 12//** Copyright (C) 1999-2006 Jānis Legzdiņš 13//** 14//** This program is free software; you can redistribute it and/or 15//** modify it under the terms of the GNU General Public License 16//** as published by the Free Software Foundation; either version 2 17//** of the License, or (at your option) any later version. 18//** 19//** This program is distributed in the hope that it will be useful, 20//** but WITHOUT ANY WARRANTY; without even the implied warranty of 21//** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22//** GNU General Public License for more details. 23//** 24//************************************************************************** 25 26//************************************************************************** 27// 28// OBJECT MOVEMENT 29// 30//************************************************************************** 31 32//========================================================================== 33// 34// Physics 35// 36//========================================================================== 37 38bool Physics(float DeltaTime) 39{ 40 float oldfloorz; 41 float CummX = 0.0; 42 float CummY = 0.0; 43 if (Sector->AffectorData && bColideWithWorld && !bNoSector) 44 { 45 // killough 3/7/98: Carry things on floor 46 // killough 3/20/98: use new sector list which reflects true members 47 // killough 3/27/98: fix carrier bug 48 // killough 4/4/98: Underwater, carry things even w/o gravity 49 50 // Move objects only if on floor or underwater, 51 // non-floating, and clipped. 52 53 int CountX = 0; 54 int CountY = 0; 55 SectorThinker SecThink; 56 for (SecThink = SectorThinker(Sector->AffectorData); SecThink; 57 SecThink = SecThink.NextAffector) 58 { 59 if (!Scroller(SecThink)) 60 { 61 continue; 62 } 63 float scrollx = Scroller(SecThink).CarryScrollX; 64 float scrolly = Scroller(SecThink).CarryScrollY; 65 if (!scrollx && !scrolly) 66 { 67 continue; 68 } 69 if (bNoGravity && (!Sector->heightsec || 70 (Sector->heightsec->bIgnoreHeightSec))) 71 { 72 continue; 73 } 74 float height = GetPlanePointZ(&Sector->floor, Origin); 75 if (Origin.z > height) 76 { 77 if (!Sector->heightsec || (Sector->heightsec->bIgnoreHeightSec)) 78 { 79 continue; 80 } 81 82 float waterheight = GetPlanePointZ(&Sector->heightsec->floor, Origin); 83 if (waterheight > height && Origin.z >= waterheight) 84 { 85 continue; 86 } 87 } 88 89 CummX += scrollx; 90 CummY += scrolly; 91 if (scrollx) 92 { 93 CountX++; 94 } 95 if (scrolly) 96 { 97 CountY++; 98 } 99 } 100 101 // Some levels designed with Boom in mind actually want things to accelerate 102 // at neighboring scrolling sector boundaries. But it is only important for 103 // non-player objects. 104 if (bIsPlayer || !Level.CompatBoomScroll) 105 { 106 if (CountX > 1) 107 { 108 CummX /= itof(CountX); 109 } 110 if (CountY > 1) 111 { 112 CummY /= itof(CountY); 113 } 114 } 115 } 116 117 CheckWater(); 118 if (!bFloatBob || !bSpecial) 119 { 120 UpdateVelocity(); 121 } 122 123 // momentum movement 124 // Handle X and Y momentums 125 oldfloorz = XYMovement(DeltaTime, CummX, CummY); 126 if (IsDestroyed()) 127 { 128 // mobj was removed 129 return false; 130 } 131 132 if (bFloatBob) 133 { 134 if (bSpecial) 135 { 136 // Floating item bobbing motion (special1 is height) 137 if (Sector->bHasExtrafloors) 138 { 139 // Make sure FloorZ is from bottom region. 140 Origin.z = ONFLOORZ; 141 LinkToWorld(); 142 } 143 FloatBobPhase += DeltaTime; 144 Origin.z = GetPlanePointZ(&Sector->floor, Origin) + Special1f + 145 Level.Game.FloatBobOffsets[ftoi(FloatBobPhase * 35.0) & 63]; 146 } 147 else 148 { 149 // Floating bobbing motion for monsters. 150 Origin.z -= Level.Game.FloatBobOffsets[ 151 ftoi(FloatBobPhase * 35.0) & 63]; 152 FloatBobPhase += DeltaTime; 153 Origin.z += Level.Game.FloatBobOffsets[ 154 ftoi(FloatBobPhase * 35.0) & 63]; 155 } 156 } 157 158 if (Velocity.z || (Origin.z != FloorZ && (!bFloatBob || (!bSpecial && 159 Origin.z - Level.Game.FloatBobOffsets[ftoi(FloatBobPhase * 35.0) & 63] != FloorZ)))) 160 { 161 if (bPassMobj && !bMissile && !Level.CompatNoPassOver) 162 { 163 // Handle Z momentum and gravity 164 EntityEx onmo = EntityEx(CheckOnmobj()); 165 if (onmo) 166 { 167 if (bIsPlayer) 168 { 169 if (Velocity.z < -DEFAULT_GRAVITY * 0.25 && !bFly && !bNoGravity) 170 { 171 PlayerLandedOnThing(); 172 } 173 } 174 if (onmo.Origin.z + onmo.Height - Origin.z <= MaxStepHeight) 175 { 176 if (bIsPlayer) 177 { 178 PlayerEx(Player).ViewHeight -= 179 onmo.Origin.z + onmo.Height - Origin.z; 180 PlayerEx(Player).DeltaViewHeight = 181 (PlayerPawn(self).ViewHeight - 182 PlayerEx(Player).ViewHeight) * 4.0; 183 } 184 Origin.z = onmo.Origin.z + onmo.Height; 185 } 186 bOnMobj = true; 187 Velocity.z = 0.0; 188 Crash(); 189 190 if (onmo.bOnmobjCopyVel) 191 { 192 Velocity.x = onmo.Velocity.x; 193 Velocity.y = onmo.Velocity.y; 194 } 195 if (onmo.Origin.z < onmo.FloorZ) 196 { 197 Origin.z += onmo.FloorZ - onmo.Origin.z; 198 if (onmo.bIsPlayer) 199 { 200 PlayerEx(onmo.Player).ViewHeight -= 201 onmo.FloorZ - onmo.Origin.z; 202 PlayerEx(onmo.Player).DeltaViewHeight = 203 (PlayerPawn(self).ViewHeight - 204 PlayerEx(onmo.Player).ViewHeight) * 4.0; 205 } 206 onmo.Origin.z = onmo.FloorZ; 207 } 208 } 209 else 210 { 211 ZMovement(DeltaTime, oldfloorz); 212 bOnMobj = false; 213 } 214 if (IsDestroyed()) 215 { 216 // entity was removed 217 return false; 218 } 219 } 220 else 221 { 222 // Handle Z momentum and gravity 223 ZMovement(DeltaTime, oldfloorz); 224 if (IsDestroyed()) 225 { 226 // entity was removed 227 return false; 228 } 229 } 230 } 231 return true; 232} 233 234//========================================================================== 235// 236// XYMovement 237// 238//========================================================================== 239 240final float XYMovement(float DeltaTime, float ScrollX, float ScrollY) 241{ 242 float ptryx; 243 float ptryy; 244 float xmove; 245 float ymove; 246 int special; 247 float oldfloorz = FloorZ; 248 249 if (bWindThrust) 250 { 251 special = Sector->special & SECSPEC_BASE_MASK; 252 switch (special) 253 { 254 case SECSPEC_WindEastSlow: 255 case SECSPEC_WindEastMedium: 256 case SECSPEC_WindEastFast: 257 Thrust(0.0, LineSpecialGameInfo(Level.Game).windTab[special - SECSPEC_WindEastSlow] * DeltaTime * 35.0); 258 break; 259 case SECSPEC_WindNorthSlow: 260 case SECSPEC_WindNorthMedium: 261 case SECSPEC_WindNorthFast: 262 Thrust(90.0, LineSpecialGameInfo(Level.Game).windTab[special - SECSPEC_WindNorthSlow] * DeltaTime * 35.0); 263 break; 264 case SECSPEC_WindSouthSlow: 265 case SECSPEC_WindSouthMedium: 266 case SECSPEC_WindSouthFast: 267 Thrust(270.0, LineSpecialGameInfo(Level.Game).windTab[special - SECSPEC_WindSouthSlow] * DeltaTime * 35.0); 268 break; 269 case SECSPEC_WindWestSlow: 270 case SECSPEC_WindWestMedium: 271 case SECSPEC_WindWestFast: 272 Thrust(180.0, LineSpecialGameInfo(Level.Game).windTab[special - SECSPEC_WindWestSlow] * DeltaTime * 35.0); 273 break; 274 } 275 } 276 277 if (Velocity.x > MAXMOVE) 278 { 279 Velocity.x = MAXMOVE; 280 } 281 else if (Velocity.x < -MAXMOVE) 282 { 283 Velocity.x = -MAXMOVE; 284 } 285 if (Velocity.y > MAXMOVE) 286 { 287 Velocity.y = MAXMOVE; 288 } 289 else if (Velocity.y < -MAXMOVE) 290 { 291 Velocity.y = -MAXMOVE; 292 } 293 294 xmove = Velocity.x * DeltaTime; 295 ymove = Velocity.y * DeltaTime; 296 297 // Carrying sectors didn't work with low speeds in BOOM. This is because 298 // BOOM relied on the speed being fast enough to accumulate despite 299 // friction. If the speed is too low, then its movement will get 300 // cancelled, and it won't accumulate to the desired speed. 301 float CarryStopSpeed = (2.0 / 3.0) * DeltaTime; 302 if (fabs(ScrollX) > CarryStopSpeed) 303 { 304 ScrollX *= CARRYFACTOR; 305 Velocity.x += ScrollX / DeltaTime; 306 } 307 if (fabs(ScrollY) > CarryStopSpeed) 308 { 309 ScrollY *= CARRYFACTOR; 310 Velocity.y += ScrollY / DeltaTime; 311 } 312 xmove += ScrollX; 313 ymove += ScrollY; 314 315 if (!xmove && !ymove) 316 { 317 if (bSkullFly) 318 { 319 // A flying mobj slammed into something 320 bSkullFly = false; 321 Velocity = vector(0.0, 0.0, 0.0); 322 if (Health > 0) 323 { 324 SetState(SeeState ? SeeState : IdleState); 325 } 326 } 327 else if (bBlasted) 328 { 329 // Reset to not blasted when momentums are gone 330 ResetBlasted(); 331 } 332 if (bTouchy && !IsSentient()) 333 { 334 // Arm a mine which has come to rest 335 bArmed = true; 336 } 337 return oldfloorz; 338 } 339 340 // Split move in multiple steps if moving too fast. 341 int Steps = 1; 342 float XStep = fabs(xmove); 343 float YStep = fabs(ymove); 344 float MaxStep = Radius - 1.0; 345 if (MaxStep <= 0.0) 346 { 347 MaxStep = MAXMOVESTEP; 348 } 349 if (XStep > MaxStep || YStep > MaxStep) 350 { 351 if (XStep > YStep) 352 { 353 Steps = ftoi(XStep / MaxStep) + 1; 354 } 355 else 356 { 357 Steps = ftoi(YStep / MaxStep) + 1; 358 } 359 } 360 float StepXMove = xmove / itof(Steps); 361 float StepYMove = ymove / itof(Steps); 362 363 int Step = 1; 364 do 365 { 366 ptryx = Origin.x + StepXMove; 367 ptryy = Origin.y + StepYMove; 368 369 tmtrace_t tmtrace; 370 if (!bNoInteraction && !TryMoveEx(&tmtrace, vector(ptryx, ptryy, Origin.z), true)) 371 { 372 // blocked move 373 if (tmtrace.BlockingMobj) 374 { 375 HitMobj(tmtrace.BlockingMobj, ptryx, ptryy); 376 } 377 else 378 { 379 HitLine(&tmtrace, DeltaTime / itof(Steps)); 380 } 381 return oldfloorz; 382 } 383 } 384 while (Step++ < Steps); 385 386 return oldfloorz; 387} 388 389//========================================================================== 390// 391// ZMovement 392// 393//========================================================================== 394 395final void ZMovement(float DeltaTime, float OldFloorZ) 396{ 397 float dist; 398 float delta; 399 float OldZ = Origin.z; 400 401 // [RH] Double gravity only if running off a ledge. Coming down from 402 // an upward thrust (e.g. a jump) should not double it. 403 // Make objects fall to their own weight. 404 if (Origin.z > FloorZ && !bNoGravity) 405 { 406 float grav = (Gravity * Level.Gravity * 407 (WaterLevel < 2 ? Sector->Gravity : Sector->Gravity / 10.0)) / 1.220703125; // 9.8 (1053.52) 408 409 if (Velocity.z == 0.0 && OldFloorZ > FloorZ && Origin.z == OldFloorZ) 410 { 411 if (WaterLevel < 2) 412 { 413 Velocity.z -= ((grav + grav) / ((Mass > 0.0) ? Mass : 1.0)) * DeltaTime; 414 } 415 else 416 { 417 Velocity.z -= (((grav + grav) / 10.0) / ((Mass > 0.0) ? Mass : 1.0)) * DeltaTime; 418 } 419 } 420 else 421 { 422 if (WaterLevel < 2) 423 { 424 Velocity.z -= (grav / ((Mass > 0.0) ? Mass : 1.0)) * DeltaTime; 425 } 426 else 427 { 428 Velocity.z -= ((grav / 10.0) / ((Mass > 0.0) ? Mass : 1.0)) * DeltaTime; 429 } 430 } 431 } 432 433 // check for smooth step up 434 if (bIsPlayer && Origin.z < FloorZ) 435 { 436 PlayerEx(Player).ViewHeight -= FloorZ - Origin.z; 437 PlayerEx(Player).DeltaViewHeight = (PlayerPawn(self).ViewHeight - 438 PlayerEx(Player).ViewHeight) * 4.0; 439 } 440 441 // adjust height 442 if (!bFloatBob) 443 { 444 Origin.z += Velocity.z * DeltaTime; 445 } 446 447 if (bFloat && !bDormant && Target) 448 { 449 // float down towards enemy if too close 450 if (!bSkullFly && !bInFloat) 451 { 452 dist = DistTo2(Target); 453 delta = Target.Origin.z + Height / 2.0 - Origin.z; 454 if (delta < 0.0 && dist < -(delta * 3.0)) 455 { 456 Velocity.z = 0.0; 457 EntityEx onmo = EntityEx(CheckOnmobj()); 458 if (((onmo && onmo.TestMobjZ()) || 459 !onmo) && TestMobjZ()) 460 { 461 Origin.z -= FloatSpeed * DeltaTime; 462 } 463 else 464 { 465 return; 466 } 467 } 468 else if (delta > 0.0 && dist < (delta * 3.0)) 469 { 470 Velocity.z = 0.0; 471 EntityEx onmo = EntityEx(CheckOnmobj()); 472 if (((onmo && onmo.TestMobjZ()) || 473 !onmo) && TestMobjZ()) 474 { 475 Origin.z += FloatSpeed * DeltaTime; 476 } 477 else 478 { 479 return; 480 } 481 } 482 } 483 } 484 if (bIsPlayer && bFly && !(Origin.z <= FloorZ) && XLevel.TicTime & 2) 485 { 486 Origin.z += sin(90.0 * 35.0 / 20.0 * XLevel.Time); 487 } 488 489 // clip movement 490 if (!bNoInteraction) 491 { 492 if (Origin.z <= FloorZ + 0.1) 493 { 494 // hit the floor 495 if (!HitFloor()) 496 { 497 return; 498 } 499 } 500 501 if (Origin.z + Height > CeilingZ) 502 { 503 // hit the ceiling 504 if (!HitCeiling()) 505 { 506 return; 507 } 508 } 509 510 CheckFakeFloorTriggers(OldZ); 511 } 512} 513 514//========================================================================== 515// 516// HitLine 517// 518//========================================================================== 519 520final void HitLine(tmtrace_t* tmtrace, float StepVelScale) 521{ 522 if (bMissile) 523 { 524 if (bBounceWalls || BounceType == BOUNCE_Doom || 525 BounceType == BOUNCE_Hexen) 526 { 527 // If number of bounces is limited. 528 if (BounceCount > 0 && --BounceCount <= 0) 529 { 530 ExplodeMissile(none); 531 return; 532 } 533 534 if (tmtrace->BlockingLine && tmtrace->BlockingLine->special == 535 LNSPEC_LineHorizon) 536 { 537 SightSound = ''; 538 Destroy(); 539 return; 540 } 541 542 // Struck a wall 543 if (bBounceWalls || BounceType == BOUNCE_Doom || 544 BounceType == BOUNCE_Hexen) 545 { 546 BounceWall(BOUNCE_VAL, WallBounceFactor); 547 if (!bNoBounceSound && !bNoWallBounceSnd && (BounceSound || SightSound)) 548 { 549 PlaySound(BounceSound ? BounceSound : SightSound, CHAN_VOICE); 550 } 551 } 552 return; 553 } 554 555 // explode a missile 556 if (tmtrace->CeilingLine && tmtrace->CeilingLine->backsector && 557 tmtrace->CeilingLine->backsector->ceiling.pic == Level.Game.skyflatnum) 558 { 559 // Hack to prevent missiles exploding against the sky. 560 // Does not handle sky floors. 561 if (bBounceSky) 562 { 563 Velocity = vector(0.0, 0.0, -1.0 * 35.0); 564 } 565 else if (bExplodeOnSky) 566 { 567 ExplodeMissile(none); 568 } 569 else 570 { 571 Destroy(); 572 } 573 return; 574 } 575 if (tmtrace->BlockingLine && tmtrace->BlockingLine->special == 576 LNSPEC_LineHorizon) 577 { 578 if (bBounceSky) 579 { 580 Velocity = vector(0.0, 0.0, -1.0 * 35.0); 581 } 582 else 583 { 584 Destroy(); 585 } 586 return; 587 } 588 ExplodeMissile(none); 589 } 590 else if (bSlide) 591 { 592 // try to slide along it 593 SlideMove(StepVelScale); 594 } 595 else 596 { 597 Velocity.x = 0.0; 598 Velocity.y = 0.0; 599 } 600} 601 602//========================================================================== 603// 604// HitMobj 605// 606//========================================================================== 607 608final void HitMobj(Entity Other, float ptryx, float ptryy) 609{ 610 float angle; 611 float speed; 612 613 if (bMissile) 614 { 615 if (bBounceOnActors || bBounceOnAllActors || 616 BounceType == BOUNCE_Doom || BounceType == BOUNCE_Hexen) 617 { 618 // Bounce against walls and non-killable objects 619 if ((bBounceOnActors || EntityEx(Other).bReflective || 620 (!Other.bIsPlayer && !EntityEx(Other).bMonster) /* || EntityEx(Other).IsSentient()*/) /* || 621 EntityEx(Other).bBounceOnAllActors*/) 622 { 623 angle = AngleMod360(atan2(Origin.y - Other.Origin.y, 624 Origin.x - Other.Origin.x) + (Random() * 16.0 - 8.0)); 625 speed = Length(Velocity); 626 speed = speed * WallBounceFactor; 627 Angles.yaw = angle; 628 Velocity.x = speed * cos(angle); 629 Velocity.y = speed * sin(angle); 630 if (!bNoBounceSound && (BounceSound || SightSound)) 631 { 632 PlaySound(BounceSound ? BounceSound : SightSound, CHAN_VOICE); 633 } 634 } 635 else 636 { 637 // Struck a player/creature 638 ExplodeMissile(EntityEx(Other)); 639 } 640 return; 641 } 642 if (EntityEx(Other).bReflective) 643 { 644 angle = EntityEx(Other).GetReflectedAngle(self); 645 if (angle != -1.0) 646 { 647 // Reflect the missile along angle 648 Angles.yaw = angle; 649 Velocity.x = (Speed / 2.0) * cos(angle); 650 Velocity.y = (Speed / 2.0) * sin(angle); 651 Velocity.z = -Velocity.z * 0.5; 652 if (bSeekerMissile) 653 { 654 Tracer = Target; 655 } 656 Target = EntityEx(Other); 657 return; 658 } 659 } 660 // Explode a missile 661 ExplodeMissile(EntityEx(Other)); 662 } 663 else if (bSlide) 664 { 665 // Try to slide along it 666 // Slide against mobj 667 if (TryMove(vector(Origin.x, ptryy, Origin.z), true)) 668 { 669 Velocity.x = 0.0; 670 } 671 else if (TryMove(vector(ptryx, Origin.y, Origin.z), true)) 672 { 673 Velocity.y = 0.0; 674 } 675 else 676 { 677 Velocity.x = 0.0; 678 Velocity.y = 0.0; 679 } 680 } 681 else 682 { 683 Velocity.x = 0.0; 684 Velocity.y = 0.0; 685 } 686} 687 688//========================================================================== 689// 690// HitFloor 691// 692//========================================================================== 693 694final bool HitFloor() 695{ 696 float vdot; 697 698 // Trigger hit floor sector actions. 699 if (Sector->ActionList && GetPlanePointZ(&Sector->floor, Origin) == FloorZ) 700 { 701 SectorAction(Sector->ActionList).TriggerAction(self, 702 SectorAction::SECSPAC_HitFloor); 703 } 704 705 // killough 11/98: touchy objects explode on impact 706 // Allow very short drops to be safe, so that a touchy can be summoned without exploding. 707 if (bTouchy && (bArmed || IsSentient()) && (Velocity.z < -5.0)) 708 { 709 bArmed = false; // Disarm 710 Damage(none, none, Health); // kill object 711 return true; 712 } 713 714 if (bMissile && (bColideWithWorld || 715 !LineSpecialGameInfo(Level.Game).bNoClipIgnoreFloor)) 716 { 717 Origin.z = FloorZ; 718 if (bBounceFloors || BounceType != BOUNCE_None) 719 { 720 FloorBounceMissile(); 721 return false; 722 } 723 if (bNoExplodeFloor) 724 { 725 // The spirit struck the ground 726 Velocity.z = 0.0; 727 HitFloorType(); 728 return false; 729 } 730 if (bIgnoreFloorStep) 731 { 732 // Minotaur floor fire can go up steps 733 return false; 734 } 735 HitFloorType(); 736 ExplodeMissile(none); 737 return false; 738 } 739 740 vdot = DotProduct(Velocity, Floor->normal); 741 if (bMonster) // Blasted mobj falling 742 { 743 if (vdot < -23.0 * 35.0) 744 { 745 MonsterFallingDamage(); 746 } 747 } 748 Origin.z = FloorZ; 749 if (bCanJump) 750 { 751 JumpTime = 0.2; // delay any jumping for a short time 752 } 753 if (vdot < -0.1) 754 { 755 // Spawn splashes, etc. 756 HitFloorType(); 757 if (!Inventory(self) && DamageType == 'Ice' && 758 vdot < -DEFAULT_GRAVITY * 0.25) 759 { 760 StateTime = 0.1; 761// Velocity = vector(0.0, 0.0, 0.0); 762 Velocity.z = 0.0; 763 return false; 764 } 765 // Do some special action when hitting the floor. 766 OnHitFloor(); 767 if (bIsPlayer) 768 { 769 PlayerEx(Player).JumpTime = 0.2; // delay any jumping for a short time 770 if (vdot < -DEFAULT_GRAVITY * 0.25 && !bNoGravity) 771 { 772 // Squat down. 773 // Decrease ViewHeight for a moment after hitting the ground 774 // (hard), and utter appropriate sound. 775 PlayerLandedOnThing(); 776 } 777 } 778 TVec Vel = vdot * Floor->normal; 779 Velocity.z -= Vel.z; 780 } 781 if (bSkullFly) 782 { 783 // The skull slammed into something 784 Velocity.z = -Velocity.z; 785 } 786 Crash(); 787 return true; 788} 789 790//========================================================================== 791// 792// HitCeiling 793// 794//========================================================================== 795 796final bool HitCeiling() 797{ 798 float vdot; 799 800 // Trigger hit ceiling sector actions. 801 if (Sector->ActionList && GetPlanePointZ(&Sector->ceiling, Origin) == 802 CeilingZ) 803 { 804 SectorAction(Sector->ActionList).TriggerAction(self, 805 SectorAction::SECSPAC_HitCeiling); 806 } 807 808 vdot = DotProduct(Velocity, Ceiling->normal); 809 if (vdot < 0.0) 810 { 811 TVec Vel = vdot * Ceiling->normal; 812 Velocity.z -= Vel.z; 813 } 814 Origin.z = CeilingZ - Height; 815 816 if (bMissile && (bColideWithWorld || 817 !LineSpecialGameInfo(Level.Game).bNoClipIgnoreFloor)) 818 { 819 if (bBounceCeilings || BounceType != BOUNCE_None) 820 { 821 CeilingBounceMissile(); 822 return false; 823 } 824 if (bIgnoreCeilingStep) 825 { 826 return false; 827 } 828 if (Ceiling->pic == Level.Game.skyflatnum) 829 { 830 if (bBounceSky) 831 { 832 Velocity = vector(0.0, 0.0, -1.0 * 35.0); 833 } 834 else if (bExplodeOnSky) 835 { 836 ExplodeMissile(none); 837 } 838 else 839 { 840 Destroy(); 841 } 842 return false; 843 } 844 ExplodeMissile(none); 845 return false; 846 } 847 if (bSkullFly) 848 { 849 // the skull slammed into something 850 Velocity.z = -Velocity.z; 851 } 852 return true; 853} 854 855//========================================================================== 856// 857// FloorBounceMissile 858// 859//========================================================================== 860 861void FloorBounceMissile() 862{ 863 float vdot; 864 865 if (HitFloorType()) 866 { 867 // Landed on some kind of liquid. 868 if (bExplodeOnWater) 869 { 870 ExplodeMissile(none); 871 return; 872 } 873 if (!bCanBounceWater) 874 { 875 Destroy(); 876 return; 877 } 878 } 879 880 // If number of bounces is limited. 881 if (BounceCount > 0 && --BounceCount <= 0) 882 { 883 ExplodeMissile(none); 884 return; 885 } 886 887 vdot = DotProduct(Velocity, Floor->normal); 888 889 if (bBounceLikeHeretic || BounceType == BOUNCE_Heretic && 890 !bMBFBounce) 891 { 892 Velocity -= 2.0 * vdot * Floor->normal; 893 Angles.yaw = atan2(Velocity.y, Velocity.x); 894 SetState(FindState('Death')); 895 return; 896 } 897 898 if (!bMBFBounce) 899 { 900 Velocity = (Velocity - 2.0 * vdot * Floor->normal) * BounceFactor; 901 Angles.yaw = atan2(Velocity.y, Velocity.x); 902 } 903 904 if (!bNoBounceSound && (BounceSound || SightSound)) 905 { 906 PlaySound(BounceSound ? BounceSound : SightSound, CHAN_VOICE); 907 } 908 909 if (bMBFBounce) 910 { 911 if (Velocity.z < (Mass * Gravity / 64.0)) 912 { 913 // Bring it to rest below a certain speed 914 Velocity.z = 0.0; 915 } 916 } 917 918 if (bBounceAutoOff || BounceType == BOUNCE_Doom) 919 { 920 if (!bNoGravity && Velocity.z < 3.0 * 35.0) 921 { 922 BounceType = BOUNCE_None; 923 } 924 } 925} 926 927//========================================================================== 928// 929// CeilingBounceMissile 930// 931//========================================================================== 932 933final void CeilingBounceMissile() 934{ 935 float vdot; 936 937 // If number of bounces is limited. 938 if (BounceCount > 0 && --BounceCount <= 0) 939 { 940 ExplodeMissile(none); 941 return; 942 } 943 944 vdot = DotProduct(Velocity, Ceiling->normal); 945 946 if (bBounceLikeHeretic || BounceType == BOUNCE_Heretic && 947 !bMBFBounce) 948 { 949 Velocity -= 2.0 * vdot * Ceiling->normal; 950 Angles.yaw = atan2(Velocity.y, Velocity.x); 951 SetState(FindState('Death')); 952 return; 953 } 954 955 // Reverse momentum here for ceiling bounce 956 Velocity = (Velocity - 2.0 * vdot * Ceiling->normal) * BounceFactor; 957 Angles.yaw = atan2(Velocity.y, Velocity.x); 958 959 if (!bNoBounceSound && (BounceSound || SightSound)) 960 { 961 PlaySound(BounceSound ? BounceSound : SightSound, CHAN_VOICE); 962 } 963} 964 965//========================================================================== 966// 967// GetReflectedAngle 968// 969//========================================================================== 970 971final float GetReflectedAngle(EntityEx Other) 972{ 973 if (Other.bDontReflect) 974 { 975 return -1.0; 976 } 977 978 float angle = atan2(Other.Origin.y - Origin.y, 979 Other.Origin.x - Origin.x); 980 981 if (bShieldReflect) 982 { 983 // Change angle for delflection/reflection 984 if (fabs(AngleMod180(angle - Angles.yaw)) > 45.0 * 45.0 / 32.0) 985 { 986 return -1.0; 987 } 988 if (Other.bDontShieldReflect) 989 { 990 return -1.0; 991 } 992 // Deflection 993 if (Random() < 0.5) 994 { 995 angle = AngleMod360(angle + 45.0); 996 } 997 else 998 { 999 angle = AngleMod360(angle - 45.0); 1000 } 1001 return angle; 1002 } 1003 1004 if (bDeflect) 1005 { 1006 // Change angle for delflection 1007 if (Random() < 0.5) 1008 { 1009 angle = AngleMod360(angle + 45.0); 1010 } 1011 else 1012 { 1013 angle = AngleMod360(angle - 45.0); 1014 } 1015 return angle; 1016 } 1017 1018 // Change angle for reflection 1019 angle = AngleMod360(angle + Random() * 16.0 - 8.0); 1020 return angle; 1021} 1022 1023//========================================================================== 1024// 1025// Crash 1026// 1027//========================================================================== 1028 1029final void Crash() 1030{ 1031 state CrashState = FindState('Crash'); 1032 if (CrashState && bCorpse && DamageType != 'Ice') 1033 { 1034 SetState(CrashState); 1035 } 1036} 1037 1038//=========================================================================== 1039// 1040// PlayerLandedOnThing 1041// 1042//=========================================================================== 1043 1044final void PlayerLandedOnThing() 1045{ 1046 PlayerEx(Player).DeltaViewHeight = Velocity.z / 8.0; 1047 FallingDamage(); 1048 if (Health > 0 && !PlayerEx(Player).MorphTime) 1049 { 1050 if (Velocity.z < -DEFAULT_GRAVITY * 0.375) 1051 { 1052 PlaySound('*grunt', CHAN_VOICE); 1053 } 1054 if ((Origin.z > FloorZ || !GetFloorType()->bLiquid) && 1055 !AreSoundsEquivalent('*grunt', '*land')) 1056 { 1057 PlaySound('*land', CHAN_BODY); 1058 } 1059 } 1060//FIXME Player.centreing = true; 1061} 1062 1063//========================================================================== 1064// 1065// FallingDamage 1066// 1067//========================================================================== 1068 1069final void FallingDamage() 1070{ 1071 int damage; 1072 float mom; 1073 float dist; 1074 1075 if (Sector->bNoFallingDamage) 1076 { 1077 return; 1078 } 1079 1080 mom = fabs(Velocity.z); 1081 1082 if (Level.bFallingDamage) 1083 { 1084 // Hexen style falling damage. 1085 if (mom <= 23.0 * 35.0) 1086 { 1087 // Not fast enough. 1088 return; 1089 } 1090 if (mom >= 63.0 * 35.0) 1091 { 1092 // Automatic death. 1093 damage = 10000; 1094 } 1095 else 1096 { 1097 dist = mom / 35.0 * 16.0 / 23.0; 1098 damage = ftoi(dist * dist / 10.0) - 24; 1099 if (Velocity.z > -39.0 * 35.0 && damage > Health && Health != 1) 1100 { 1101 // No-death threshold. 1102 damage = Health - 1; 1103 } 1104 } 1105 } 1106 else if (Level.bOldFallingDamage) 1107 { 1108 // ZDoom style falling damage, less damaging. 1109 if (mom <= 19.0 * 35.0) 1110 { 1111 // Not fast enough. 1112 return; 1113 } 1114 if (mom >= 84.0 * 35.0) 1115 { 1116 // Automatic death. 1117 damage = 10000; 1118 } 1119 else 1120 { 1121 mom = mom / 35.0; 1122 damage = (ftoi(mom * mom * 11.0 / 128.0) - 30) / 2; 1123 if (damage < 1) 1124 { 1125 damage = 1; 1126 } 1127 } 1128 } 1129 else if (Level.bStrifeFallingDamage) 1130 { 1131 // Strife style falling damage, very strong. 1132 if (mom <= 20.0 * 35.0) 1133 { 1134 // Not fast enough. 1135 return; 1136 } 1137 damage = ftoi(mom * (8192.0 / 3125.0 / 35.0)); 1138 } 1139 else 1140 { 1141 return; 1142 } 1143 1144 Damage(none, none, damage, 'Falling'); 1145 LineSpecialLevelInfo(Level).NoiseAlert(self, self); 1146} 1147 1148//========================================================================== 1149// 1150// MonsterFallingDamage 1151// 1152//========================================================================== 1153 1154final void MonsterFallingDamage() 1155{ 1156 int damage; 1157 float mom; 1158 1159 if (!Level.bMonsterFallingDamage) 1160 { 1161 return; 1162 } 1163 if (Sector->bNoFallingDamage) 1164 { 1165 return; 1166 } 1167 1168 mom = fabs(Velocity.z) / 35.0; 1169 if (mom > 35.0) 1170 { 1171 // automatic death 1172 damage = 10000; 1173 } 1174 else 1175 { 1176 damage = ftoi((mom - 23.0) * 6.0); 1177 } 1178 damage = 10000; // always kill 'em 1179 Damage(none, none, damage, 'Falling'); 1180} 1181 1182//========================================================================== 1183// 1184// ResetBlasted 1185// 1186//========================================================================== 1187 1188final void ResetBlasted() 1189{ 1190 bBlasted = false; 1191 if (!bIceCorpse) 1192 { 1193 bSlide = false; 1194 } 1195} 1196 1197//========================================================================== 1198// 1199// OnHitFloor 1200// 1201//========================================================================== 1202 1203void OnHitFloor() 1204{ 1205} 1206 1207//========================================================================== 1208// 1209// CheckSplash 1210// 1211// Checks for splashes caused by explosions 1212// 1213//========================================================================== 1214 1215void CheckSplash(float distance) 1216{ 1217 if (Origin.z <= FloorZ + distance) 1218 { 1219 // Explosion splashes never alert monsters. This is because A_Explode has 1220 // a separate parameter for that so this would get in the way of proper 1221 // behavior. 1222 HitFloorType(); 1223 } 1224} 1225 1226//========================================================================== 1227// 1228// HitFloorType 1229// 1230//========================================================================== 1231 1232final bool HitFloorType() 1233{ 1234 EntityEx A; 1235 TVec org; 1236 bool smallsplash = false; 1237 VTerrainInfo* TInfo; 1238 VSplashInfo* SInfo; 1239 1240 if (FloorZ != GetPlanePointZ(&Sector->floor, Origin)) 1241 { 1242 // don't splash if landing on the edge above water/lava/etc.... 1243 return false; 1244 } 1245 1246 // Things that don't splash go here 1247 if (bFloatBob || bNoSplash) 1248 { 1249 return false; 1250 } 1251 1252 TInfo = GetFloorType(); 1253 1254 // Small splash for small masses 1255 if (Mass < 10.0 || bSmallSplash) 1256 smallsplash = true; 1257 1258 if (TInfo->DamageAmount && bIsPlayer && 1259 XLevel.TicTime & TInfo->DamageTimeMask) 1260 { 1261 Damage(none, none, TInfo->DamageAmount, TInfo->DamageType); 1262 } 1263 1264 SInfo = GetSplashInfo(TInfo->Splash); 1265 if (!SInfo) 1266 { 1267 return TInfo->bLiquid; 1268 } 1269 1270 org = Origin; 1271 org.z = FloorZ; 1272 1273 if (smallsplash) 1274 { 1275 if (SInfo->SmallClass) 1276 { 1277 A = Spawn(class<EntityEx>(SInfo->SmallClass), org); 1278 A.FloorClip += SInfo->SmallClip; 1279 if (SInfo->SmallSound) 1280 { 1281 A.PlaySound(SInfo->SmallSound, CHAN_VOICE); 1282 } 1283 } 1284 } 1285 else 1286 { 1287 if (SInfo->BaseClass) 1288 { 1289 A = Spawn(class<EntityEx>(SInfo->BaseClass), org); 1290 if (SInfo->Sound && !SInfo->ChunkClass) 1291 { 1292 A.PlaySound(SInfo->Sound, CHAN_VOICE); 1293 } 1294 } 1295 if (SInfo->ChunkClass) 1296 { 1297 A = Spawn(class<EntityEx>(SInfo->ChunkClass), org); 1298 A.Target = self; 1299 A.Velocity.x = (Random() - Random()) * SInfo->ChunkXVelMul * 35.0; 1300 A.Velocity.y = (Random() - Random()) * SInfo->ChunkYVelMul * 35.0; 1301 A.Velocity.z = (SInfo->ChunkBaseZVel + Random() * 1302 SInfo->ChunkZVelMul) * 35.0; 1303 if (SInfo->Sound) 1304 { 1305 A.PlaySound(SInfo->Sound, CHAN_VOICE); 1306 } 1307 } 1308 if (SInfo->Sound && !SInfo->BaseClass && !SInfo->ChunkClass) 1309 { 1310 PlaySound(SInfo->Sound, CHAN_BODY); 1311 } 1312 if (!SInfo->bNoAlert && bIsPlayer) 1313 { 1314 LineSpecialLevelInfo(Level).NoiseAlert(self, self, true); 1315 } 1316 } 1317 return TInfo->bLiquid; 1318} 1319 1320//=========================================================================== 1321// 1322// GetFloorType 1323// 1324//=========================================================================== 1325 1326final VTerrainInfo* GetFloorType() 1327{ 1328 return TerrainType(Floor->pic); 1329} 1330 1331//========================================================================== 1332// 1333// HandleFloorclip 1334// 1335//========================================================================== 1336 1337final void HandleFloorclip() 1338{ 1339 if (bFloorClip) 1340 { 1341 VTerrainInfo* TInfo = GetFloorType(); 1342 if (Origin.z == FloorZ && TInfo->bLiquid) 1343 { 1344 FloorClip = TInfo->FootClip; 1345 } 1346 else 1347 { 1348 FloorClip = 0.0; 1349 } 1350 } 1351} 1352 1353//========================================================================== 1354// 1355// ApplyFriction 1356// 1357//========================================================================== 1358 1359final void ApplyFriction() 1360{ 1361 float dot; 1362 1363 if (bMissile || bSkullFly) 1364 { 1365 // no friction for missiles ever 1366 return; 1367 } 1368 1369 if (Origin.z > FloorZ && !bOnMobj && WaterLevel < 2 && !bFly && 1370 !bFallingFriction) 1371 { 1372 // no friction when airborne 1373 return; 1374 } 1375 1376 // Clip velocity 1377 if (Origin.z <= FloorZ) 1378 { 1379 dot = DotProduct(Velocity, Floor->normal); 1380 if (dot < 0.0) 1381 { 1382 TVec Vel; 1383 Vel = dot * Floor->normal; 1384 Velocity.x -= Vel.x; 1385 Velocity.y -= Vel.y; 1386 } 1387 } 1388 1389 if (bCorpse) 1390 { 1391 // Don't stop sliding if halfway off a step with some momentum 1392 if (Velocity.x > 0.25 * 35.0 || Velocity.x < -0.25 * 35.0 || 1393 Velocity.y > 0.25 * 35.0 || Velocity.y < -0.25 * 35.0) 1394 { 1395 if (FloorZ > GetPlanePointZ(&Sector->floor, Origin) || 1396 DropOffZ != FloorZ) 1397 { 1398 return; 1399 } 1400 } 1401 } 1402 1403 if (Velocity.x > -STOPSPEED && Velocity.x < STOPSPEED && 1404 Velocity.y > -STOPSPEED && Velocity.y < STOPSPEED && 1405 (!bIsPlayer || (!Player.ForwardMove && !Player.SideMove))) 1406 { 1407 if (bIsPlayer) 1408 { 1409 // if in a walking frame, stop moving 1410 if (StateIsInRange(State, SeeState, none, 4)) 1411 { 1412 SetState(IdleState); 1413 } 1414 } 1415 Velocity.x = 0.0; 1416 Velocity.y = 0.0; 1417 } 1418 else 1419 { 1420 // slow down 1421 Velocity.x -= Velocity.x * (GetFriction() * Level.Game.frametime); 1422 Velocity.y -= Velocity.y * (GetFriction() * Level.Game.frametime); 1423 } 1424} 1425 1426//=========================================================================== 1427// 1428// GetFriction 1429// 1430//=========================================================================== 1431 1432final float GetFriction() 1433{ 1434 if (WaterLevel > 1) 1435 { 1436 return FRICTION_WATER; 1437 } 1438 if (bFly && Origin.z > FloorZ && !bOnMobj) 1439 { 1440 return FRICTION_FLY; 1441 } 1442 if ((Sector->special & SECSPEC_BASE_MASK) == SECSPEC_FrictionLow) 1443 { 1444 return FRICTION_LOW; 1445 } 1446 VTerrainInfo* TInfo = GetFloorType(); 1447 if (TInfo->Friction) 1448 { 1449 return TInfo->Friction; 1450 } 1451 if (Sector->special & SECSPEC_FRICTION_MASK) 1452 { 1453 return Sector->Friction; 1454 } 1455 return FRICTION_NORMAL; 1456} 1457