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