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