1//************************************************************************** 2//** 3//** ## ## ## ## ## #### #### ### ### 4//** ## ## ## ## ## ## ## ## ## ## #### #### 5//** ## ## ## ## ## ## ## ## ## ## ## ## ## ## 6//** ## ## ######## ## ## ## ## ## ## ## ### ## 7//** ### ## ## ### ## ## ## ## ## ## 8//** # ## ## # #### #### ## ## 9//** 10//** $Id: EntityEx.LineAttack.vc 4325 2010-07-15 23:11:16Z 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// AIMING 29// 30//************************************************************************** 31 32//========================================================================== 33// 34// AimLineAttack 35// 36// Sets linetaget and aim_slope when a target is aimed at. 37// 38//========================================================================== 39 40final EntityEx AimLineAttack(out TVec OutDir, TAVec angles, float distance) 41{ 42 float x2; 43 float y2; 44 float topangle; 45 float botangle; 46 TVec dir; 47 intercept_t* in; 48 EntityEx linetarget; // who got hit (or NULL) 49 50 // Height if not aiming up or down 51 // ???: use slope for monsters? 52 float aim_z; 53 float aim_range; 54 float aim_slope; 55 TVec aim_dir; 56 float aim_topslope; 57 float aim_bottomslope; // slopes to top and bottom of target 58 float aim_range2d; 59 60 AngleVector(&angles, &aim_dir); 61 x2 = Origin.x + distance * aim_dir.x; 62 y2 = Origin.y + distance * aim_dir.y; 63 aim_z = Origin.z + Height / 2.0 - FloorClip; 64 if (bIsPlayer) 65 { 66 aim_z += PlayerPawn(Player.MO).AttackZOffset; 67 } 68 else 69 { 70 aim_z += 8.0; 71 } 72 aim_range2d = Length(vector(distance * aim_dir.x, distance * aim_dir.y, 73 0.0)); 74 75 // can't shoot outside view angles 76 topangle = AngleMod180(-angles.pitch + 30.0); 77 botangle = AngleMod180(-angles.pitch - 30.0); 78 if (topangle > 89.0) 79 topangle = 89.0; 80 if (botangle < -89.0) 81 botangle = -89.0; 82 aim_topslope = tan(topangle); 83 aim_bottomslope = tan(botangle); 84 85 aim_range = distance; 86 linetarget = none; 87 88 foreach PathTraverse(in, Origin.x, Origin.y, x2, y2, 89 PT_ADDLINES | PT_ADDTHINGS) 90 { 91 line_t* li; 92 EntityEx th; 93 float thingtopslope; 94 float thingbottomslope; 95 float dist; 96 float slope; 97 opening_t* open; 98 99 if (in->bIsALine) 100 { 101 TVec hit_point; 102 103 li = in->line; 104 105 if (!(li->flags & ML_TWOSIDED)) 106 { 107 break; // stop 108 } 109 110 // Crosses a two sided line. 111 // A two sided line will restrict 112 // the possible target ranges. 113 dist = aim_range * in->frac; 114 if (dist < 0.01) 115 { 116 // Shooter is on the line. 117 continue; 118 } 119 hit_point = Origin + dist * aim_dir; 120 open = LineOpenings(li, hit_point); 121 open = FindOpening(open, hit_point.z, hit_point.z); 122 123 if (!open || open->bottom >= open->top) 124 { 125 break; // stop 126 } 127 128 dist = aim_range2d * in->frac; 129 slope = (open->bottom - aim_z) / dist; 130 if (slope > aim_bottomslope) 131 { 132 aim_bottomslope = slope; 133 } 134 135 slope = (open->top - aim_z) / dist; 136 if (slope < aim_topslope) 137 { 138 aim_topslope = slope; 139 } 140 141 if (aim_topslope <= aim_bottomslope) 142 { 143 break; // stop 144 } 145 146 continue; // shot continues 147 } 148 149 // shoot a thing 150 th = EntityEx(in->Thing); 151 if (th == self) 152 continue; // can't shoot self 153 154 if (!th.bShootable) 155 continue; // corpse or something 156 157 if (th.bCantAutoAim) 158 { 159 // Can't auto-aim at pods 160 continue; 161 } 162 163 if (IsTeammate(th)) 164 { 165 // don't aim at fellow co-op players 166 continue; 167 } 168 169 // check angles to see if the thing can be aimed at 170 dist = aim_range2d * in->frac; 171 if (dist < 0.01) 172 { 173 // Too close, must be above or below. 174 continue; 175 } 176 thingtopslope = (th.Origin.z + th.Height - aim_z) / dist; 177 178 if (thingtopslope < aim_bottomslope) 179 continue; // shot over the thing 180 181 thingbottomslope = (th.Origin.z - aim_z) / dist; 182 183 if (thingbottomslope > aim_topslope) 184 continue; // shot under the thing 185 186 // this thing can be hit! 187 if (thingtopslope > aim_topslope) 188 thingtopslope = aim_topslope; 189 190 if (thingbottomslope < aim_bottomslope) 191 thingbottomslope = aim_bottomslope; 192 193 aim_slope = (thingtopslope + thingbottomslope) / 2.0; 194 linetarget = th; 195 196 break; // don't go any farther 197 } 198 199 if (linetarget) 200 { 201 angles.pitch = -atan(aim_slope); 202 } 203 AngleVector(&angles, &dir); 204 OutDir = dir; 205 return linetarget; 206} 207 208//=========================================================================== 209// 210// Aim 211// 212// Sets a slope so a near miss is at aproximately the height of the 213// intended target 214// 215//=========================================================================== 216 217final EntityEx Aim(out TVec OutDir, float distance, optional float yaw) 218{ 219 TAVec ang; 220 TVec dir; 221 EntityEx LineTarget; 222 223 // see which target is to be aimed at 224 ang = Angles; 225 if (specified_yaw) 226 { 227 ang.yaw = yaw; 228 } 229 if (bIsPlayer && !PlayerEx(Player).bAutoAim) 230 { 231 AngleVector(&ang, &dir); 232 OutDir = dir; 233 } 234 235 // Try to aim at a target. This is done even when autoaim is off so that 236 // we get a LineTarget that is needed for seeker missiles. 237 LineTarget = AimLineAttack(dir, ang, distance); 238 if (!LineTarget) 239 { 240 ang.yaw = AngleMod360(ang.yaw + 45.0 / 8.0); 241 LineTarget = AimLineAttack(dir, ang, distance); 242 if (!LineTarget) 243 { 244 ang.yaw = AngleMod360(ang.yaw - 45.0 / 4.0); 245 LineTarget = AimLineAttack(dir, ang, distance); 246 if (!LineTarget) 247 { 248 ang.yaw = AngleMod360(ang.yaw + 45.0 / 8.0); 249 AngleVector(&ang, &dir); 250 } 251 } 252 } 253 254 if (!bIsPlayer || PlayerEx(Player).bAutoAim) 255 { 256 OutDir = dir; 257 } 258 return LineTarget; 259} 260 261//========================================================================== 262// 263// AimEx 264// 265//========================================================================== 266 267final EntityEx AimEx(out TVec OutDir, float Range, float AngleInc, 268 int NumSteps, optional float FinalRange) 269{ 270 int i; 271 TAVec angles; 272 TVec vforward; 273 EntityEx LineTarget; 274 275 for (i = 0; i < NumSteps; i++) 276 { 277 // Try to the left 278 angles = Angles; 279 angles.yaw = AngleMod360(angles.yaw + itof(i) * AngleInc); 280 LineTarget = AimLineAttack(OutDir, angles, Range); 281 if (LineTarget) 282 { 283 return LineTarget; 284 } 285 286 // Try to the right 287 angles = Angles; 288 angles.yaw = AngleMod360(angles.yaw - itof(i) * AngleInc); 289 LineTarget = AimLineAttack(OutDir, angles, Range); 290 if (LineTarget) 291 { 292 return LineTarget; 293 } 294 } 295 296 if (FinalRange) 297 { 298 // Didn't find any creatures, so try to strike any walls 299 angles = Angles; 300 LineTarget = AimLineAttack(OutDir, angles, FinalRange); 301 } 302 else 303 { 304 AngleVector(&Angles, &vforward); 305 OutDir = vforward; 306 } 307 return LineTarget; 308} 309 310//************************************************************************** 311// 312// SHOOTING 313// 314//************************************************************************** 315 316//========================================================================== 317// 318// ShootHitPlane 319// 320//========================================================================== 321 322final bool ShootHitPlane(sec_plane_t* plane, TVec linestart, TVec lineend, 323 float range, class<EntityEx> PuffType, TVec* OutHitPoint) 324{ 325 float org_dist; 326 float hit_dist; 327 328 if (plane->flags & SPF_NOBLOCKSHOOT) 329 { 330 // Doesn't block shooting 331 return true; 332 } 333 org_dist = DotProduct(linestart, plane->normal) - plane->dist; 334 if (org_dist < 0.0) 335 { 336 // Don't shoot back side 337 return true; 338 } 339 hit_dist = DotProduct(lineend, plane->normal) - plane->dist; 340 if (hit_dist >= 0.0) 341 { 342 // Didn't hit plane 343 return true; 344 } 345 346 // Hit plane 347 if (plane->pic == Level.Game.skyflatnum) 348 { 349 // don't shoot the sky! 350 if (OutHitPoint) 351 { 352 *OutHitPoint = lineend; 353 } 354 return false; 355 } 356 357 // If we are shooting floor or ceiling we are adjusting position 358 // to spawn puff on floor or ceiling, not on wall 359 lineend -= (lineend - linestart) * hit_dist / (hit_dist - org_dist); 360 361 // position a bit closer 362 lineend += 4.0 * plane->normal; 363 364 // Spawn bullet puffs. 365 SpawnPuff(lineend, range, PuffType, false); 366 367 if (OutHitPoint) 368 { 369 *OutHitPoint = lineend; 370 } 371 372 // don't go any farther 373 return false; 374} 375 376//========================================================================== 377// 378// ShootCheckPlanes 379// 380//========================================================================== 381 382final bool ShootCheckPlanes(sector_t* sec, TVec linestart, TVec lineend, 383 float range, class<EntityEx> PuffType, TVec* OutHitPoint) 384{ 385 sec_region_t *reg; 386 sec_region_t *startreg; 387 388 startreg = PointInRegion(sec, linestart); 389 for (reg = startreg; reg; reg = reg->next) 390 { 391 if (!ShootHitPlane(reg->floor, linestart, lineend, range, PuffType, 392 OutHitPoint)) 393 { 394 // Hit floor 395 return false; 396 } 397 if (!ShootHitPlane(reg->ceiling, linestart, lineend, range, PuffType, 398 OutHitPoint)) 399 { 400 // Hit ceiling 401 return false; 402 } 403 } 404 for (reg = startreg->prev; reg; reg = reg->prev) 405 { 406 if (!ShootHitPlane(reg->floor, linestart, lineend, range, PuffType, 407 OutHitPoint)) 408 { 409 // Hit floor 410 return false; 411 } 412 if (!ShootHitPlane(reg->ceiling, linestart, lineend, range, PuffType, 413 OutHitPoint)) 414 { 415 // Hit ceiling 416 return false; 417 } 418 } 419 return true; 420} 421 422//========================================================================== 423// 424// LineAttack 425// 426//========================================================================== 427 428final int LineAttack(TVec Dir, float Distance, int LADamage, 429 class<EntityEx> PuffType, optional bool NoAttackGhosts, 430 optional TVec* OutHitPoint, optional name DmgType) 431{ 432 TVec Dst; 433 intercept_t* in; 434 TVec LineStart; 435 TVec LineEnd; 436 TVec ShootOrigin; 437 438 ShootOrigin = Origin; 439 ShootOrigin.z += Height * 0.5 - FloorClip; 440 if (bIsPlayer) 441 { 442 ShootOrigin.z += PlayerPawn(Player.MO).AttackZOffset; 443 } 444 else 445 { 446 ShootOrigin.z += 8.0; 447 } 448 449 Dst = ShootOrigin + Distance * Dir; 450 LineStart = ShootOrigin; 451 foreach PathTraverse(in, Origin.x, Origin.y, Dst.x, Dst.y, 452 PT_ADDLINES | PT_ADDTHINGS) 453 { 454 TVec hit_point; 455 line_t* li; 456 EntityEx th; 457 458 if (in->bIsALine) 459 { 460 sector_t *sec; 461 462 li = in->line; 463 hit_point = ShootOrigin + (Distance * in->frac) * Dir; 464 if (li->flags & ML_TWOSIDED && PointOnPlaneSide(ShootOrigin, li)) 465 { 466 sec = li->backsector; 467 } 468 else 469 { 470 sec = li->frontsector; 471 } 472 473 LineEnd = hit_point; 474 475 // Check for shooting floor or ceiling 476 if (!ShootCheckPlanes(sec, LineStart, LineEnd, Distance, PuffType, 477 OutHitPoint)) 478 { 479 return false; 480 } 481 482 LineStart = LineEnd; 483 484 if (Level.CompatTrace && li->frontsector == li->backsector) 485 { 486 continue; 487 } 488 489 // Execute line special after checking for hitting floor or ceiling 490 // when we know that it actally hits line 491 if (li->special && !bNoTrigger) 492 { 493 LineSpecialLevelInfo(Level).ActivateLine(li, self, 0, SPAC_Impact); 494 } 495 496 if (li->flags & ML_TWOSIDED) 497 { 498 // crosses a two sided line 499 opening_t *open; 500 float opentop = 0.0; 501 502 open = LineOpenings(li, hit_point); 503 if (open) 504 { 505 opentop = open->top; 506 } 507 while (open) 508 { 509 if (open->bottom <= hit_point.z && open->top >= hit_point.z) 510 { 511 // shot continues 512 break; 513 } 514 open = open->next; 515 } 516 if (open) 517 { 518 continue; 519 } 520 if (li->frontsector->ceiling.pic == Level.Game.skyflatnum && 521 li->backsector->ceiling.pic == Level.Game.skyflatnum && 522 hit_point.z > opentop) 523 { 524 // it's a sky hack wall 525 if (OutHitPoint) 526 { 527 *OutHitPoint = hit_point; 528 } 529 return false; 530 } 531 } 532 533 if (OutHitPoint) 534 { 535 *OutHitPoint = hit_point; 536 } 537 538 if (li->special == LNSPEC_LineHorizon) 539 { 540 // Don't spawn puffs on sky. 541 return false; 542 } 543 544 // Hit line 545 546 // position a bit closer 547 hit_point -= 4.0 * Dir; 548 549 // Spawn bullet puffs. 550 SpawnPuff(hit_point, Distance, PuffType, false); 551 552 // don't go any farther 553 return false; 554 } 555 556 // shoot a thing 557 th = EntityEx(in->Thing); 558 559 if (th == self) 560 continue; // can't shoot self 561 562 if (!th.bShootable) 563 continue; // corpse or something 564 565 // check angles to see if the thing can be aimed at 566 hit_point = ShootOrigin + (Distance * in->frac) * Dir; 567 568 if (th.Origin.z + th.Height < hit_point.z) 569 continue; // shot over the thing 570 571 if (th.Origin.z > hit_point.z) 572 continue; // shot under the thing 573 574 // hit thing 575 // position a bit closer 576 hit_point -= 10.0 * Dir; 577 578 // check for physical attacks on a ghost 579 if (th.bGhost && NoAttackGhosts) 580 { 581 continue; 582 } 583 584 if (OutHitPoint) 585 { 586 *OutHitPoint = hit_point; 587 } 588 589 bool UseAxeBlood = bIsPlayer && PlayerEx(Player).ReadyWeapon && 590 PlayerEx(Player).ReadyWeapon.bAxeBlood; 591 bool UseBloodSplatter = UseAxeBlood || bBloodSplatter || 592 (bIsPlayer && PlayerEx(Player).ReadyWeapon && 593 PlayerEx(Player).ReadyWeapon.bBloodSplatter); 594 595 // Spawn bullet puffs or blod spots, depending on target type. 596 if (PuffType.default.bPuffOnActors || th.bNoBlood || 597 th.bInvulnerable || th.bDormant) 598 { 599 SpawnPuff(hit_point, Distance, PuffType, true); 600 } 601 if (!UseBloodSplatter && !th.bNoBlood && !th.bInvulnerable && 602 !th.bDormant) 603 { 604 th.SpawnBlood(hit_point, LADamage); 605 } 606 if (LADamage && UseBloodSplatter) 607 { 608 if (!th.bNoBlood && !th.bInvulnerable && !th.bDormant) 609 { 610 if (UseAxeBlood) 611 { 612 th.SpawnBloodSplatter2(hit_point); 613 } 614 if (P_Random() < 192) 615 { 616 th.SpawnBloodSplatter(hit_point, LADamage); 617 } 618 } 619 } 620 621 if (LADamage) 622 { 623 bool NoArmor = false; 624 // Allow bPierceArmor on weapons as well. 625 if (bIsPlayer && PlayerEx(Player).ReadyWeapon && 626 PlayerEx(Player).ReadyWeapon.bPierceArmor) 627 { 628 NoArmor = true; 629 } 630 th.Damage(self, self, LADamage, DmgType, NoArmor); 631 } 632 633 // don't go any farther 634 return false; 635 } 636 LineEnd = Dst; 637 if (!ShootCheckPlanes(XLevel.PointInSector(Dst), LineStart, LineEnd, 638 Distance, PuffType, OutHitPoint)) 639 { 640 return false; 641 } 642 643 if (PuffType.default.ActiveSound) 644 { 645 // Play miss sound. 646 PlaySound(PuffType.default.ActiveSound, CHAN_WEAPON); 647 } 648 if (PuffType.default.bAlwaysPuff) 649 { 650 SpawnPuff(LineEnd, Distance, PuffType, false); 651 } 652 return true; 653} 654 655//************************************************************************** 656// 657// RAILGUN 658// 659//************************************************************************** 660 661//========================================================================== 662// 663// RailHitPlane 664// 665//========================================================================== 666 667final bool RailHitPlane(sec_plane_t* plane, TVec linestart, out TVec lineend, 668 float range, class<EntityEx> PuffType) 669{ 670 float org_dist; 671 float hit_dist; 672 673 if (plane->flags & SPF_NOBLOCKSHOOT) 674 { 675 // Doesn't block shooting 676 return true; 677 } 678 org_dist = DotProduct(linestart, plane->normal) - plane->dist; 679 if (org_dist < 0.0) 680 { 681 // Don't shoot back side 682 return true; 683 } 684 hit_dist = DotProduct(lineend, plane->normal) - plane->dist; 685 if (hit_dist >= 0.0) 686 { 687 // Didn't hit plane 688 return true; 689 } 690 691 // Hit plane 692 if (plane->pic == Level.Game.skyflatnum) 693 { 694 // don't shoot the sky! 695 return false; 696 } 697 698 // If we are shooting floor or ceiling we are adjusting position 699 // to spawn puff on floor or ceiling, not on wall 700 lineend -= (lineend - linestart) * hit_dist / (hit_dist - org_dist); 701 702 // position a bit closer 703 lineend += 4.0 * plane->normal; 704 705 if (PuffType) 706 { 707 // Spawn bullet puffs. 708 SpawnPuff(lineend, range, PuffType, false); 709 } 710 711 // don't go any farther 712 return false; 713} 714 715//========================================================================== 716// 717// RailCheckPlanes 718// 719//========================================================================== 720 721final bool RailCheckPlanes(sector_t* sec, TVec linestart, out TVec lineend, 722 float range, class<EntityEx> PuffType) 723{ 724 sec_region_t *reg; 725 sec_region_t *startreg; 726 727 startreg = PointInRegion(sec, linestart); 728 for (reg = startreg; reg; reg = reg->next) 729 { 730 if (!RailHitPlane(reg->floor, linestart, lineend, range, PuffType)) 731 { 732 // Hit floor 733 return false; 734 } 735 if (!RailHitPlane(reg->ceiling, linestart, lineend, range, PuffType)) 736 { 737 // Hit ceiling 738 return false; 739 } 740 } 741 for (reg = startreg->prev; reg; reg = reg->prev) 742 { 743 if (!RailHitPlane(reg->floor, linestart, lineend, range, PuffType)) 744 { 745 // Hit floor 746 return false; 747 } 748 if (!RailHitPlane(reg->ceiling, linestart, lineend, range, PuffType)) 749 { 750 // Hit ceiling 751 return false; 752 } 753 } 754 return true; 755} 756 757//========================================================================== 758// 759// RailAttack 760// 761//========================================================================== 762 763final void RailAttack(TVec Dir, float Offset, int RailDamage, 764 optional int Col1, optional int Col2, optional float MaxDiff, 765 optional class<EntityEx> PuffType, optional bool Silent, 766 optional bool DontPierce) 767{ 768 TVec Dst; 769 intercept_t* in; 770 TVec LineStart; 771 TVec LineEnd; 772 TVec ShootOrigin; 773 774 if (!Silent) 775 { 776 PlaySound('weapons/railgf', CHAN_WEAPON); 777 } 778 779 if (!specified_PuffType) 780 { 781 PuffType = BulletPuff; 782 } 783 name DmgType = 'Railgun'; 784 if (PuffType && PuffType.default.DamageType) 785 { 786 DmgType = PuffType.default.DamageType; 787 } 788 789 ShootOrigin = Origin; 790 ShootOrigin.z += Height * 0.5 - FloorClip; 791 if (bIsPlayer) 792 { 793 ShootOrigin.z += PlayerPawn(Player.MO).AttackZOffset; 794 } 795 else 796 { 797 ShootOrigin.z += 8.0; 798 } 799 ShootOrigin.x += Offset * cos(Angles.yaw - 90.0); 800 ShootOrigin.y += Offset * sin(Angles.yaw - 90.0); 801 802 float Distance = 8192.0; 803 Dst = ShootOrigin + Distance * Dir; 804 LineStart = ShootOrigin; 805 bool DidHit = false; 806 foreach PathTraverse(in, Origin.x, Origin.y, Dst.x, Dst.y, 807 PT_ADDLINES | PT_ADDTHINGS) 808 { 809 TVec hit_point; 810 line_t* li; 811 EntityEx th; 812 813 if (in->bIsALine) 814 { 815 sector_t *sec; 816 817 li = in->line; 818 hit_point = ShootOrigin + (Distance * in->frac) * Dir; 819 if (li->flags & ML_TWOSIDED && PointOnPlaneSide(ShootOrigin, li)) 820 { 821 sec = li->backsector; 822 } 823 else 824 { 825 sec = li->frontsector; 826 } 827 828 LineEnd = hit_point; 829 830 // Check for shooting floor or ceiling 831 if (!RailCheckPlanes(sec, LineStart, LineEnd, Distance, PuffType)) 832 { 833 DidHit = true; 834 break; 835 } 836 837 LineStart = LineEnd; 838 839 // Execute line special after checking for hitting floor or ceiling 840 // when we know that it actally hits line 841 if (li->special && !bNoTrigger) 842 { 843 LineSpecialLevelInfo(Level).ActivateLine(li, self, 0, SPAC_Impact); 844 } 845 846 if (li->flags & ML_TWOSIDED) 847 { 848 // crosses a two sided line 849 opening_t *open; 850 float opentop = 0.0; 851 852 open = LineOpenings(li, hit_point); 853 if (open) 854 { 855 opentop = open->top; 856 } 857 while (open) 858 { 859 if (open->bottom <= hit_point.z && open->top >= hit_point.z) 860 { 861 // shot continues 862 break; 863 } 864 open = open->next; 865 } 866 if (open) 867 { 868 continue; 869 } 870 if (li->frontsector->ceiling.pic == Level.Game.skyflatnum && 871 li->backsector->ceiling.pic == Level.Game.skyflatnum && 872 hit_point.z > opentop) 873 { 874 // it's a sky hack wall 875 DidHit = true; 876 break; 877 } 878 } 879 880 // Hit line 881 882 // position a bit closer 883 hit_point -= 4.0 * Dir; 884 885 if (PuffType) 886 { 887 // Spawn bullet puffs. 888 SpawnPuff(hit_point, Distance, PuffType, false); 889 } 890 891 // don't go any farther 892 LineEnd = hit_point; 893 DidHit = true; 894 break; 895 } 896 897 // shoot a thing 898 th = EntityEx(in->Thing); 899 900 if (th == self) 901 { 902 continue; // can't shoot self 903 } 904 905 if (!th.bShootable) 906 { 907 continue; // corpse or something 908 } 909 910 // check angles to see if the thing can be aimed at 911 hit_point = ShootOrigin + (Distance * in->frac) * Dir; 912 913 if (th.Origin.z + th.Height < hit_point.z) 914 { 915 continue; // shot over the thing 916 } 917 918 if (th.Origin.z > hit_point.z) 919 { 920 continue; // shot under the thing 921 } 922 923 // Invulnerable things completely block the shot. 924 if (th.bInvulnerable) 925 { 926 DidHit = true; 927 LineEnd = hit_point; 928 break; 929 } 930 931 // hit thing 932 // position a bit closer 933 hit_point -= 10.0 * Dir; 934 935 // Spawn bullet puffs or blood spots, depending on target type. 936 if (th.bNoBlood || th.bInvulnerable || th.bDormant) 937 { 938 if (PuffType) 939 { 940 SpawnPuff(hit_point, Distance, PuffType, true); 941 } 942 } 943 else 944 { 945 th.SpawnBlood(hit_point, RailDamage); 946 } 947 th.Damage(self, self, RailDamage, DmgType); 948 if (th && DontPierce) 949 { 950 // We did hit a thing and we can't pierce 951 // so we can stop now... 952 DidHit = true; 953 LineEnd = hit_point; 954 break; 955 } 956 } 957 958 if (!DidHit) 959 { 960 LineEnd = Dst; 961 RailCheckPlanes(XLevel.PointInSector(Dst), LineStart, LineEnd, 962 Distance, PuffType); 963 } 964 965 PlayerEx P; 966 foreach AllActivePlayers(P) 967 { 968 P.ClientRailTrail(ShootOrigin, LineEnd, Col1, Col2, MaxDiff); 969 } 970} 971