1 #include "Directories.h"
2 #include "Font_Control.h"
3 #include "Handle_Items.h"
4 #include "LoadSaveRealObject.h"
5 #include "Physics.h"
6 #include "Structure.h"
7 #include "TileDat.h"
8 #include "WCheck.h"
9 #include "Timer_Control.h"
10 #include "Isometric_Utils.h"
11 #include "LOS.h"
12 #include "WorldMan.h"
13 #include "Event_Pump.h"
14 #include "Sound_Control.h"
15 #include "Soldier_Control.h"
16 #include "Interface.h"
17 #include "Interface_Items.h"
18 #include "Explosion_Control.h"
19 #include "Tile_Animation.h"
20 #include "Message.h"
21 #include "Weapons.h"
22 #include "Structure_Wrap.h"
23 #include "Overhead.h"
24 #include "Animation_Control.h"
25 #include "Text.h"
26 #include "Random.h"
27 #include "LightEffects.h"
28 #include "OppList.h"
29 #include "World_Items.h"
30 #include "Environment.h"
31 #include "SoundMan.h"
32 #include "MemMan.h"
33 #include "Debug.h"
34 #include "FileMan.h"
35 #include "Items.h"
36 #include "Campaign.h"
37 #include "SkillCheck.h"
38 
39 #include "ContentManager.h"
40 #include "GameInstance.h"
41 #include "Logger.h"
42 
43 #include <algorithm>
44 #include <math.h>
45 #include <stdexcept>
46 
47 #define NO_TEST_OBJECT				0
48 #define TEST_OBJECT_NO_COLLISIONS		1
49 #define TEST_OBJECT_ANY_COLLISION		2
50 #define TEST_OBJECT_NOTWALLROOF_COLLISIONS	3
51 
52 #define OUTDOORS_START_ANGLE			(FLOAT)( PI/4 )
53 #define INDOORS_START_ANGLE			(FLOAT)( PI/30 )
54 //#define INDOORS_START_ANGLE			(FLOAT)( 0 )
55 #define GLAUNCHER_START_ANGLE			(FLOAT)( PI/8 )
56 #define GLAUNCHER_HIGHER_LEVEL_START_ANGLE	(FLOAT)( PI/6 )
57 
58 #define GET_THROW_HEIGHT( l )			(INT16)( ( l * 256 ) )
59 #define GET_SOLDIER_THROW_HEIGHT( l )		(INT16)( ( l * 256 ) + STANDING_HEIGHT )
60 
61 #define GET_OBJECT_LEVEL( z )			( (INT8)( ( z + 10 ) / HEIGHT_UNITS ) )
62 #define OBJECT_DETONATE_ON_IMPACT( o )		( ( o->Obj.usItem == MORTAR_SHELL ) ) // && ( o->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) )
63 
64 
65 #define MAX_INTEGRATIONS			8
66 
67 #define TIME_MULTI				1.8
68 
69 //#define TIME_MULTI				2.2
70 
71 
72 #define DELTA_T					( 1.0 * TIME_MULTI )
73 
74 
75 #define GRAVITY					( 9.8 * 2.5 )
76 //#define GRAVITY				( 9.8 * 2.8 )
77 
78 
79 #define NUM_OBJECT_SLOTS			50
80 static REAL_OBJECT ObjectSlots[NUM_OBJECT_SLOTS];
81 UINT32  guiNumObjectSlots = 0;
82 BOOLEAN fDampingActive = FALSE;
83 //real   Kdl = (float)0.5; // LINEAR DAMPENING ( WIND RESISTANCE )
84 float   Kdl = (float)( 0.1 * TIME_MULTI ); // LINEAR DAMPENING ( WIND RESISTANCE )
85 
86 #define EPSILONV				0.5
87 #define EPSILONP				(float)0.01
88 #define EPSILONPZ				3
89 
90 #define CALCULATE_OBJECT_MASS( m )		( (float)( m * 2 ) )
91 #define SCALE_VERT_VAL_TO_HORZ( f )		( ( f / HEIGHT_UNITS ) * CELL_X_SIZE )
92 #define SCALE_HORZ_VAL_TO_VERT( f )		( ( f / CELL_X_SIZE ) * HEIGHT_UNITS )
93 
94 
95 #define REALOBJ2ID(o) 				((o) - ObjectSlots)
96 
97 
98 /// OBJECT POOL FUNCTIONS
GetFreeObjectSlot(void)99 static REAL_OBJECT* GetFreeObjectSlot(void)
100 {
101 	REAL_OBJECT*             i   = ObjectSlots;
102 	REAL_OBJECT const* const end = i + guiNumObjectSlots;
103 	for (; i != end; ++i)
104 	{
105 		if (!i->fAllocated) return i;
106 	}
107 	if (i != endof(ObjectSlots))
108 	{
109 		++guiNumObjectSlots;
110 		return i;
111 	}
112 	throw std::runtime_error("Out of physics object slots");
113 }
114 
115 
RecountObjectSlots(void)116 static void RecountObjectSlots(void)
117 {
118 	INT32 uiCount;
119 
120 	for(uiCount=guiNumObjectSlots-1; (uiCount >=0) ; uiCount--)
121 	{
122 		if( ( ObjectSlots[uiCount].fAllocated ) )
123 		{
124 			guiNumObjectSlots=(UINT32)(uiCount+1);
125 			return;
126 		}
127 	}
128 
129 	guiNumObjectSlots = 0;
130 }
131 
132 
CreatePhysicalObject(OBJECTTYPE const * const pGameObj,float const dLifeLength,float const xPos,float const yPos,float const zPos,float const xForce,float const yForce,float const zForce,SOLDIERTYPE * const owner,UINT8 const ubActionCode,SOLDIERTYPE * const target)133 REAL_OBJECT* CreatePhysicalObject(OBJECTTYPE const* const pGameObj, float const dLifeLength, float const xPos, float const yPos, float const zPos, float const xForce, float const yForce, float const zForce, SOLDIERTYPE* const owner, UINT8 const ubActionCode, SOLDIERTYPE* const target)
134 {
135 	REAL_OBJECT* const o = GetFreeObjectSlot();
136 	*o = REAL_OBJECT{};
137 
138 	o->Obj = *pGameObj;
139 
140 	FLOAT mass = CALCULATE_OBJECT_MASS(GCM->getItem(pGameObj->usItem)->getWeight());
141 	if (mass == 0) mass = 10;
142 
143 	// OK, mass determines the smoothness of the physics integration
144 	// For gameplay, we will use mass for maybe max throw distance
145 	mass = 60;
146 
147 	o->dLifeLength             = dLifeLength;
148 	o->fAllocated              = TRUE;
149 	o->fAlive                  = TRUE;
150 	o->fApplyFriction          = FALSE;
151 	o->uiSoundID               = NO_SAMPLE;
152 	o->OneOverMass             = 1 / mass;
153 	o->Position.x              = xPos;
154 	o->Position.y              = yPos;
155 	o->Position.z              = zPos;
156 	o->fVisible                = TRUE;
157 	o->owner                   = owner;
158 	o->ubActionCode            = ubActionCode;
159 	o->target                  = target;
160 	o->fDropItem               = TRUE;
161 	o->ubLastTargetTakenDamage = NOBODY;
162 	o->fFirstTimeMoved         = TRUE;
163 	o->InitialForce.x          = SCALE_VERT_VAL_TO_HORZ(xForce);
164 	o->InitialForce.y          = SCALE_VERT_VAL_TO_HORZ(yForce);
165 	o->InitialForce.z          = zForce;
166 	o->InitialForce            = VMultScalar(&o->InitialForce, (float)(1.5 / TIME_MULTI));
167 	o->sGridNo                 = MAPROWCOLTOPOS(((INT16)yPos / CELL_Y_SIZE), ((INT16)xPos / CELL_X_SIZE));
168 	o->pNode                   = 0;
169 	o->pShadow                 = 0;
170 
171 	// If gridno not equal to NOWHERE, use sHeight of land
172 	if (o->sGridNo != NOWHERE)
173 	{
174 		float const h = CONVERT_PIXELS_TO_HEIGHTUNITS(gpWorldLevelData[o->sGridNo].sHeight);
175 		o->Position.z                   += h;
176 		o->EndedWithCollisionPosition.z += h;
177 	}
178 
179 	SLOGD("NewPhysics Object");
180 	return o;
181 }
182 
183 
RemoveRealObject(REAL_OBJECT * const o)184 static BOOLEAN RemoveRealObject(REAL_OBJECT* const o)
185 {
186 	CHECKF(ObjectSlots <= o && o < endof(ObjectSlots));
187 
188 	o->fAllocated = FALSE;
189 
190 	RecountObjectSlots();
191 
192 	return( TRUE );
193 }
194 
195 
196 static void SimulateObject(REAL_OBJECT* pObject, float deltaT);
197 
198 
SimulateWorld()199 void SimulateWorld(  )
200 {
201 	UINT32					cnt;
202 	REAL_OBJECT		*pObject;
203 
204 
205 	if ( COUNTERDONE( PHYSICSUPDATE ) )
206 	{
207 		RESETCOUNTER( PHYSICSUPDATE );
208 
209 		for( cnt = 0;cnt < guiNumObjectSlots; cnt++)
210 		{
211 			// CHECK FOR ALLOCATED
212 			if( ObjectSlots[ cnt ].fAllocated )
213 			{
214 				// Get object
215 				pObject = &( ObjectSlots[ cnt ] );
216 
217 				SimulateObject( pObject, (float)DELTA_T );
218 			}
219 		}
220 	}
221 }
222 
223 
224 static void PhysicsDeleteObject(REAL_OBJECT* pObject);
225 
226 
RemoveAllPhysicsObjects()227 void RemoveAllPhysicsObjects( )
228 {
229 	UINT32					cnt;
230 
231 	for( cnt = 0;cnt < guiNumObjectSlots; cnt++)
232 	{
233 		// CHECK FOR ALLOCATED
234 		if( ObjectSlots[ cnt ].fAllocated )
235 		{
236 			PhysicsDeleteObject( &(ObjectSlots[ cnt ]) );
237 		}
238 	}
239 }
240 
241 
242 static BOOLEAN PhysicsComputeForces(REAL_OBJECT* pObject);
243 static BOOLEAN PhysicsHandleCollisions(REAL_OBJECT* pObject, INT32* piCollisionID, float DeltaTime);
244 static BOOLEAN PhysicsIntegrate(REAL_OBJECT* pObject, float DeltaTime);
245 static BOOLEAN PhysicsMoveObject(REAL_OBJECT* pObject);
246 static BOOLEAN PhysicsUpdateLife(REAL_OBJECT* pObject, float DeltaTime);
247 
248 
SimulateObject(REAL_OBJECT * pObject,float deltaT)249 static void SimulateObject(REAL_OBJECT* pObject, float deltaT)
250 {
251 	float   DeltaTime = 0;
252 	float   CurrentTime = 0;
253 	float   TargetTime = DeltaTime;
254 	INT32   iCollisionID;
255 	BOOLEAN fEndThisObject = FALSE;
256 
257 	if ( !PhysicsUpdateLife( pObject, (float)deltaT ) )
258 	{
259 		return;
260 	}
261 
262 	if ( pObject->fAlive )
263 	{
264 		CurrentTime = 0;
265 		TargetTime = (float)deltaT;
266 
267 		// Do subtime here....
268 		DeltaTime = (float)deltaT / (float)10;
269 
270 		if ( !PhysicsComputeForces( pObject ) )
271 		{
272 			return;
273 		}
274 
275 		while( CurrentTime < TargetTime )
276 		{
277 			if ( !PhysicsIntegrate( pObject, DeltaTime ) )
278 			{
279 				fEndThisObject = TRUE;
280 				break;
281 			}
282 
283 			if ( !PhysicsHandleCollisions( pObject, &iCollisionID, DeltaTime  ) )
284 			{
285 				fEndThisObject = TRUE;
286 				break;
287 			}
288 
289 			if ( iCollisionID != COLLISION_NONE )
290 			{
291 				break;
292 			}
293 
294 			CurrentTime += DeltaTime;
295 		}
296 
297 		if ( fEndThisObject )
298 		{
299 			return;
300 		}
301 
302 		if ( !PhysicsMoveObject( pObject ) )
303 		{
304 			return;
305 		}
306 
307 	}
308 }
309 
310 
PhysicsComputeForces(REAL_OBJECT * pObject)311 static BOOLEAN PhysicsComputeForces(REAL_OBJECT* pObject)
312 {
313 	vector_3			vTemp;
314 
315 	// Calculate forces
316 	pObject->Force = pObject->InitialForce;
317 
318 	pObject->Force.z -= (float)GRAVITY;
319 
320 	// Set intial force to zero
321 	pObject->InitialForce = VMultScalar( &(pObject->InitialForce ), 0 );
322 
323 	if ( pObject->fApplyFriction )
324 	{
325 		vTemp = VMultScalar( &(pObject->Velocity), -pObject->AppliedMu );
326 		pObject->Force = VAdd( &(vTemp), &(pObject->Force) );
327 
328 		pObject->fApplyFriction = FALSE;
329 	}
330 
331 	if( fDampingActive )
332 	{
333 		vTemp = VMultScalar( &(pObject->Velocity), -Kdl );
334 		pObject->Force = VAdd( &(vTemp), &(pObject->Force) );
335 
336 	}
337 
338 	return( TRUE );
339 }
340 
341 
342 static void HandleArmedObjectImpact(REAL_OBJECT* pObject);
343 
344 
PhysicsUpdateLife(REAL_OBJECT * pObject,float DeltaTime)345 static BOOLEAN PhysicsUpdateLife(REAL_OBJECT* pObject, float DeltaTime)
346 {
347 	UINT8 bLevel = 0;
348 
349 	pObject->dLifeSpan += DeltaTime;
350 
351 	// End life if time has ran out or we are stationary
352 	if ( pObject->dLifeLength != -1 )
353 	{
354 		if ( pObject->dLifeSpan > pObject->dLifeLength )
355 		{
356 			pObject->fAlive = FALSE;
357 		}
358 
359 	}
360 
361 	// End life if we are out of bounds....
362 	if ( !GridNoOnVisibleWorldTile( pObject->sGridNo ) )
363 	{
364 		pObject->fAlive = FALSE;
365 	}
366 
367 	if ( !pObject->fAlive )
368 	{
369 		pObject->fAlive = FALSE;
370 
371 		if ( !pObject->fTestObject )
372 		{
373 			if ( pObject->uiSoundID != NO_SAMPLE )
374 			{
375 				SoundStop( pObject->uiSoundID );
376 			}
377 
378 			if ( pObject->ubActionCode == THROW_ARM_ITEM && !pObject->fInWater )
379 			{
380 				HandleArmedObjectImpact( pObject );
381 			}
382 			else
383 			{
384 				// If we are in water, and we are a sinkable item...
385 				if ( !pObject->fInWater || !( GCM->getItem(pObject->Obj.usItem)->getFlags() & ITEM_SINKS ) )
386 				{
387 					if ( pObject->fDropItem )
388 					{
389 						// ATE: If we have collided with roof last...
390 						if ( pObject->iOldCollisionCode == COLLISION_ROOF )
391 						{
392 							bLevel = 1;
393 						}
394 
395 						// ATE; If an armed object, don't add....
396 						if ( pObject->ubActionCode != THROW_ARM_ITEM )
397 						{
398 							AddItemToPool(pObject->sGridNo, &pObject->Obj, VISIBLE, bLevel, 0, -1);
399 						}
400 					}
401 				}
402 			}
403 
404 			// Make impact noise....
405 			if ( pObject->Obj.usItem == ROCK || pObject->Obj.usItem == ROCK2 )
406 			{
407 				MakeNoise(pObject->owner, pObject->sGridNo, 0, 9 + PreRandom(9), NOISE_ROCK_IMPACT);
408 			}
409 			else if ( GCM->getItem(pObject->Obj.usItem)->isGrenade() )
410 			{
411 				MakeNoise(pObject->owner, pObject->sGridNo, 0, 9 + PreRandom(9), NOISE_GRENADE_IMPACT);
412 			}
413 
414 			if ( !pObject->fTestObject && pObject->iOldCollisionCode == COLLISION_GROUND )
415 			{
416 				PlayLocationJA2Sample(pObject->sGridNo, THROW_IMPACT_2, MIDVOLUME, 1);
417 			}
418 
419 			ReduceAttackBusyCount(pObject->owner, FALSE);
420 
421 			// ATE: Handle end of animation...
422 			if ( pObject->fCatchAnimOn )
423 			{
424 				pObject->fCatchAnimOn = FALSE;
425 
426 				// Get intended target
427 				SOLDIERTYPE* const pSoldier = pObject->target;
428 
429 				// Catch anim.....
430 				switch( gAnimControl[ pSoldier->usAnimState ].ubHeight )
431 				{
432 					case ANIM_STAND:
433 
434 						pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
435 						EVENT_InitNewSoldierAnim( pSoldier, END_CATCH, 0 , FALSE );
436 						break;
437 
438 					case ANIM_CROUCH:
439 
440 						pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
441 						EVENT_InitNewSoldierAnim( pSoldier, END_CROUCH_CATCH, 0 , FALSE );
442 						break;
443 				}
444 
445 				PlayLocationJA2Sample(pSoldier->sGridNo, CATCH_OBJECT, MIDVOLUME, 1);
446 			}
447 		}
448 
449 		PhysicsDeleteObject( pObject );
450 		return( FALSE );
451 	}
452 
453 	return( TRUE );
454 }
455 
456 
PhysicsIntegrate(REAL_OBJECT * pObject,float DeltaTime)457 static BOOLEAN PhysicsIntegrate(REAL_OBJECT* pObject, float DeltaTime)
458 {
459 	vector_3			vTemp;
460 
461 	// Save old position
462 	pObject->OldPosition = pObject->Position;
463 	pObject->OldVelocity = pObject->Velocity;
464 
465 	vTemp = VMultScalar( &(pObject->Velocity), DeltaTime );
466 	pObject->Position = VAdd( &(pObject->Position), &vTemp );
467 
468 	// Save test TargetPosition
469 	if ( pObject->fTestPositionNotSet )
470 	{
471 		pObject->TestTargetPosition = pObject->Position;
472 	}
473 
474 	vTemp = VMultScalar( &(pObject->Force), ( DeltaTime * pObject->OneOverMass ) );
475 	pObject->Velocity = VAdd( &(pObject->Velocity), &vTemp );
476 
477 	if ( pObject->fPotentialForDebug )
478 	{
479 		SLOGD(ST::format("Object {}: Force     {} {} {}", REALOBJ2ID(pObject),
480 			pObject->Force.x, pObject->Force.y, pObject->Force.z));
481 		SLOGD(ST::format("Object {}: Velocity  {} {} {}", REALOBJ2ID(pObject),
482 			pObject->Velocity.x, pObject->Velocity.y, pObject->Velocity.z));
483 		SLOGD(ST::format("Object {}: Position  {} {} {}", REALOBJ2ID(pObject),
484 			pObject->Position.x, pObject->Position.y, pObject->Position.z));
485 		SLOGD(ST::format("Object {}: Delta Pos {} {} {}", REALOBJ2ID(pObject),
486 			pObject->OldPosition.x - pObject->Position.x, pObject->OldPosition.y - pObject->Position.y,
487 			pObject->OldPosition.z - pObject->Position.z));
488 	}
489 
490 	if ( pObject->Obj.usItem == MORTAR_SHELL && !pObject->fTestObject && pObject->ubActionCode == THROW_ARM_ITEM )
491 	{
492 		// Start soud if we have reached our max height
493 		if ( pObject->OldVelocity.z >= 0 && pObject->Velocity.z < 0 )
494 		{
495 			if ( pObject->uiSoundID == NO_SAMPLE )
496 			{
497 				pObject->uiSoundID =	PlayJA2Sample(MORTAR_WHISTLE, HIGHVOLUME, 1, MIDDLEPAN);
498 			}
499 		}
500 	}
501 
502 	return( TRUE );
503 }
504 
505 
506 static BOOLEAN PhysicsCheckForCollisions(REAL_OBJECT* pObject, INT32* piCollisionID);
507 static void PhysicsResolveCollision(REAL_OBJECT* pObject, vector_3* pVelocity, vector_3* pNormal, float CoefficientOfRestitution);
508 
509 
PhysicsHandleCollisions(REAL_OBJECT * pObject,INT32 * piCollisionID,float DeltaTime)510 static BOOLEAN PhysicsHandleCollisions(REAL_OBJECT* pObject, INT32* piCollisionID, float DeltaTime)
511 {
512 	FLOAT dDeltaX, dDeltaY, dDeltaZ;
513 
514 
515 	if ( PhysicsCheckForCollisions( pObject, piCollisionID ) )
516 	{
517 
518 		dDeltaX = pObject->Position.x - pObject->OldPosition.x;
519 		dDeltaY = pObject->Position.y - pObject->OldPosition.y;
520 		dDeltaZ = pObject->Position.z - pObject->OldPosition.z;
521 
522 		if ( dDeltaX <= EPSILONV && dDeltaX >= -EPSILONV &&
523 			dDeltaY <= EPSILONV && dDeltaY >= -EPSILONV )
524 		{
525 			pObject->sConsecutiveZeroVelocityCollisions++;
526 		}
527 
528 		if ( pObject->sConsecutiveZeroVelocityCollisions > 3 )
529 		{
530 			// We will continue with our Z velocity
531 			pObject->Velocity.x = 0;
532 			pObject->Velocity.y = 0;
533 
534 			// Check that we are not colliding with structure z
535 			//if ( *piCollisionID == COLLISION_STRUCTURE_Z || *piCollisionID == COLLISION_ROOF )
536 			if ( *piCollisionID == COLLISION_STRUCTURE_Z || *piCollisionID == COLLISION_ROOF || *piCollisionID == COLLISION_GROUND )
537 			{
538 				pObject->Velocity.z = 0;
539 
540 				// Set us not alive!
541 				pObject->fAlive = FALSE;
542 			}
543 
544 			*piCollisionID = COLLISION_NONE;
545 		}
546 		else
547 		{
548 			// Set position back to before collision
549 			pObject->Position = pObject->OldPosition;
550 			// Set old position!
551 			pObject->OldPosition.x = pObject->Position.y - dDeltaX;
552 			pObject->OldPosition.y = pObject->Position.x - dDeltaY;
553 			pObject->OldPosition.z = pObject->Position.z - dDeltaZ;
554 
555 			PhysicsResolveCollision( pObject, &(pObject->CollisionVelocity), &(pObject->CollisionNormal), pObject->CollisionElasticity );
556 		}
557 
558 		if ( pObject->Position.z < 0 )
559 		{
560 			pObject->Position.z = 0;
561 		}
562 		//otherwise, continue falling downwards!
563 
564 		// TO STOP?
565 
566 		// Check for delta position values
567 		if (dDeltaZ <= EPSILONP && dDeltaZ >= -EPSILONP &&
568 			dDeltaY <= EPSILONP && dDeltaY >= -EPSILONP &&
569 			dDeltaX <= EPSILONP && dDeltaX >= -EPSILONP)
570 		{
571 			//pObject->fAlive = FALSE;
572 			//return( FALSE );
573 		}
574 
575 		// Check for repeated collisions...
576 		//if ( pObject->iOldCollisionCode == COLLISION_ROOF || pObject->iOldCollisionCode == COLLISION_GROUND || pObject->iOldCollisionCode == COLLISION_WATER )
577 		{
578 			// ATE: This is a safeguard
579 			if (pObject->sConsecutiveCollisions > 30)
580 			{
581 				pObject->fAlive = FALSE;
582 				return( FALSE );
583 			}
584 		}
585 
586 
587 		// Check for -ve velocity still...
588 		//if ( pObject->Velocity.z <= EPSILONV && pObject->Velocity.z >= -EPSILONV &&
589 		//		pObject->Velocity.y <= EPSILONV && pObject->Velocity.y >= -EPSILONV &&
590 		//		pObject->Velocity.x <= EPSILONV && pObject->Velocity.x >= -EPSILONV )
591 		//{
592 			//PhysicsDeleteObject( pObject );
593 		//	pObject->fAlive = FALSE;
594 		//	return( FALSE );
595 		//}
596 	}
597 
598 	return( TRUE );
599 }
600 
601 
PhysicsDeleteObject(REAL_OBJECT * pObject)602 static void PhysicsDeleteObject(REAL_OBJECT* pObject)
603 {
604 	if ( pObject->fAllocated )
605 	{
606 		if ( pObject->pNode != NULL )
607 		{
608 			RemoveStructFromLevelNode( pObject->sLevelNodeGridNo, pObject->pNode );
609 		}
610 
611 		if ( pObject->pShadow != NULL )
612 		{
613 			RemoveShadowFromLevelNode( pObject->sLevelNodeGridNo, pObject->pShadow );
614 		}
615 
616 		RemoveRealObject(pObject);
617 	}
618 }
619 
620 
621 static BOOLEAN CheckForCatcher(REAL_OBJECT* pObject, UINT16 usStructureID);
622 static void CheckForObjectHittingMerc(REAL_OBJECT* pObject, UINT16 usStructureID);
623 
624 
PhysicsCheckForCollisions(REAL_OBJECT * pObject,INT32 * piCollisionID)625 static BOOLEAN PhysicsCheckForCollisions(REAL_OBJECT* pObject, INT32* piCollisionID)
626 {
627 	vector_3 vTemp;
628 	FLOAT    dDeltaX, dDeltaY, dDeltaZ, dX, dY, dZ;
629 	INT32    iCollisionCode = COLLISION_NONE;
630 	BOOLEAN  fDoCollision = FALSE;
631 	FLOAT    dElasity = 1;
632 	UINT16   usStructureID;
633 	FLOAT    dNormalX, dNormalY, dNormalZ;
634 	INT16    sGridNo;
635 
636 	// Checkf for collisions
637 	dX = pObject->Position.x;
638 	dY = pObject->Position.y;
639 	dZ = pObject->Position.z;
640 
641 	vTemp.x = 0;
642 	vTemp.y = 0;
643 	vTemp.z = 0;
644 
645 	dDeltaX = dX - pObject->OldPosition.x;
646 	dDeltaY = dY - pObject->OldPosition.y;
647 	dDeltaZ = dZ - pObject->OldPosition.z;
648 
649 	//Round delta pos to nearest 0.01
650 	//dDeltaX = (float)( (int)dDeltaX * 100 ) / 100;
651 	//dDeltaY = (float)( (int)dDeltaY * 100 ) / 100;
652 	//dDeltaZ = (float)( (int)dDeltaZ * 100 ) / 100;
653 
654 	// SKIP FIRST GRIDNO, WE'LL COLLIDE WITH OURSELVES....
655 	if ( pObject->fTestObject != TEST_OBJECT_NO_COLLISIONS )
656 	{
657 		iCollisionCode = CheckForCollision( dX, dY, dZ, dDeltaX, dDeltaY, dDeltaZ, &usStructureID, &dNormalX, &dNormalY, &dNormalZ );
658 	}
659 	else if ( pObject->fTestObject == TEST_OBJECT_NO_COLLISIONS )
660 	{
661 		iCollisionCode = COLLISION_NONE;
662 
663 		// Are we on a downward slope?
664 		if ( dZ < pObject->TestZTarget && dDeltaZ < 0 )
665 		{
666 			if (pObject->fTestPositionNotSet )
667 			{
668 				if ( pObject->TestZTarget > 32 )
669 				{
670 					pObject->fTestPositionNotSet = FALSE;
671 					pObject->TestZTarget         = 0;
672 				}
673 				else
674 				{
675 					iCollisionCode = COLLISION_GROUND;
676 				}
677 			}
678 			else
679 			{
680 				iCollisionCode = COLLISION_GROUND;
681 			}
682 		}
683 	}
684 
685 
686 	// If a test object and we have collided with something ( should only be ground ( or roof? ) )
687 	// Or destination?
688 	if ( pObject->fTestObject == TEST_OBJECT_ANY_COLLISION )
689 	{
690 		if ( iCollisionCode != COLLISION_GROUND && iCollisionCode != COLLISION_ROOF && iCollisionCode != COLLISION_WATER && iCollisionCode != COLLISION_NONE )
691 		{
692 			pObject->fTestEndedWithCollision = TRUE;
693 			pObject->fAlive = FALSE;
694 			return( FALSE );
695 		}
696 	}
697 
698 	if ( pObject->fTestObject == TEST_OBJECT_NOTWALLROOF_COLLISIONS )
699 	{
700 		// So we don't collide with ourselves.....
701 		if ( iCollisionCode != COLLISION_WATER && iCollisionCode != COLLISION_GROUND && iCollisionCode != COLLISION_NONE &&
702 			iCollisionCode != COLLISION_ROOF && iCollisionCode != COLLISION_INTERIOR_ROOF &&
703 			iCollisionCode != COLLISION_WALL_SOUTHEAST && iCollisionCode != COLLISION_WALL_SOUTHWEST &&
704 			iCollisionCode != COLLISION_WALL_NORTHEAST && iCollisionCode != COLLISION_WALL_NORTHWEST )
705 		{
706 			if ( pObject->fFirstTimeMoved || pObject->sFirstGridNo == pObject->sGridNo )
707 			{
708 				iCollisionCode = COLLISION_NONE;
709 			}
710 
711 			// If we are NOT a wall or window, ignore....
712 			if ( pObject->uiNumTilesMoved < 4 )
713 			{
714 				switch( iCollisionCode )
715 				{
716 					case COLLISION_MERC:
717 					case COLLISION_STRUCTURE:
718 					case COLLISION_STRUCTURE_Z:
719 						// Set to no collision ( we shot past )
720 						iCollisionCode = COLLISION_NONE;
721 						break;
722 				}
723 			}
724 		}
725 
726 
727 		switch( iCollisionCode )
728 		{
729 			// End test with any collision NOT a wall, roof...
730 			case COLLISION_STRUCTURE:
731 			case COLLISION_STRUCTURE_Z:
732 				// OK, if it's mercs... don't stop
733 				if ( usStructureID >= INVALID_STRUCTURE_ID )
734 				{
735 					pObject->fTestEndedWithCollision = TRUE;
736 
737 					if ( !pObject->fEndedWithCollisionPositionSet )
738 					{
739 						pObject->fEndedWithCollisionPositionSet = TRUE;
740 						pObject->EndedWithCollisionPosition = pObject->Position;
741 					}
742 					iCollisionCode = COLLISION_NONE;
743 				}
744 				else
745 				{
746 					if ( !pObject->fEndedWithCollisionPositionSet )
747 					{
748 						pObject->fEndedWithCollisionPositionSet = TRUE;
749 						pObject->EndedWithCollisionPosition = pObject->Position;
750 					}
751 				}
752 				break;
753 
754 			case COLLISION_ROOF:
755 
756 				if ( !pObject->fEndedWithCollisionPositionSet )
757 				{
758 					pObject->fEndedWithCollisionPositionSet = TRUE;
759 					pObject->EndedWithCollisionPosition = pObject->Position;
760 				}
761 				break;
762 
763 			case COLLISION_WATER:
764 			case COLLISION_GROUND:
765 			case COLLISION_MERC:
766 			case COLLISION_INTERIOR_ROOF:
767 			case COLLISION_NONE:
768 			case COLLISION_WINDOW_SOUTHEAST:
769 			case COLLISION_WINDOW_SOUTHWEST:
770 			case COLLISION_WINDOW_NORTHEAST:
771 			case COLLISION_WINDOW_NORTHWEST:
772 				// Here we just keep going..
773 				break;
774 
775 			default:
776 				// THis is for walls, windows, etc
777 				// here, we set test ended with collision, but keep going...
778 				pObject->fTestEndedWithCollision = TRUE;
779 				break;
780 		}
781 	}
782 
783 
784 	if ( pObject->fTestObject != TEST_OBJECT_NOTWALLROOF_COLLISIONS )
785 	{
786 		if ( iCollisionCode != COLLISION_WATER && iCollisionCode != COLLISION_GROUND && iCollisionCode != COLLISION_NONE &&
787 			iCollisionCode != COLLISION_ROOF && iCollisionCode != COLLISION_INTERIOR_ROOF &&
788 			iCollisionCode != COLLISION_WALL_SOUTHEAST && iCollisionCode != COLLISION_WALL_SOUTHWEST &&
789 			iCollisionCode != COLLISION_WALL_NORTHEAST && iCollisionCode != COLLISION_WALL_NORTHWEST )
790 		{
791 			// So we don't collide with ourselves.....
792 			if ( pObject->fFirstTimeMoved || pObject->sFirstGridNo == pObject->sGridNo )
793 			{
794 				iCollisionCode = COLLISION_NONE;
795 			}
796 
797 			// If we are NOT a wall or window, ignore....
798 			if ( pObject->uiNumTilesMoved < 4 )
799 			{
800 				switch( iCollisionCode )
801 				{
802 				case COLLISION_MERC:
803 				case COLLISION_STRUCTURE:
804 				case COLLISION_STRUCTURE_Z:
805 
806 					// Set to no collision ( we shot past )
807 					iCollisionCode = COLLISION_NONE;
808 					break;
809 				}
810 			}
811 
812 		}
813 	}
814 
815 	*piCollisionID = iCollisionCode;
816 
817 
818 	// If We hit the ground
819 	if ( iCollisionCode > COLLISION_NONE )
820 	{
821 		if ( pObject->iOldCollisionCode == iCollisionCode )
822 		{
823 			pObject->sConsecutiveCollisions++;
824 		}
825 		else
826 		{
827 			pObject->sConsecutiveCollisions = 1;
828 		}
829 
830 		if ( iCollisionCode == COLLISION_WINDOW_NORTHWEST || iCollisionCode == COLLISION_WINDOW_NORTHEAST || iCollisionCode == COLLISION_WINDOW_SOUTHWEST || iCollisionCode == COLLISION_WINDOW_SOUTHEAST )
831 		{
832 			if ( !pObject->fTestObject )
833 			{
834 				// Break window!
835 				STLOGD("Object {}: Collision Window", REALOBJ2ID(pObject));
836 
837 				sGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
838 
839 				WindowHit(sGridNo, usStructureID, FALSE, TRUE);
840 			}
841 			*piCollisionID = COLLISION_NONE;
842 			return( FALSE );
843 		}
844 
845 		// ATE: IF detonate on impact, stop now!
846 		if ( OBJECT_DETONATE_ON_IMPACT( pObject ) )
847 		{
848 			pObject->fAlive = FALSE;
849 			return( TRUE );
850 		}
851 
852 		if ( iCollisionCode == COLLISION_GROUND )
853 		{
854 			vTemp.x = 0;
855 			vTemp.y = 0;
856 			vTemp.z = -1;
857 
858 			pObject->fApplyFriction = TRUE;
859 			//pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
860 			pObject->AppliedMu = (float)(0.34 * TIME_MULTI );
861 
862 			//dElasity = (float)1.5;
863 			dElasity = (float)1.3;
864 
865 			fDoCollision = TRUE;
866 
867 			if ( !pObject->fTestObject && !pObject->fHaveHitGround )
868 			{
869 				PlayLocationJA2Sample(pObject->sGridNo, THROW_IMPACT_2, MIDVOLUME, 1);
870 			}
871 
872 			pObject->fHaveHitGround = TRUE;
873 		}
874 		else if ( iCollisionCode == COLLISION_WATER )
875 		{
876 			ANITILE_PARAMS	AniParams;
877 			ANITILE						*pNode;
878 
879 			// Continue going...
880 			pObject->fApplyFriction = TRUE;
881 			pObject->AppliedMu = (float)(1.54 * TIME_MULTI );
882 
883 			sGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
884 
885 			// Make thing unalive...
886 			pObject->fAlive = FALSE;
887 
888 			// If first time...
889 			if ( pObject->fVisible )
890 			{
891 				if ( pObject->fTestObject == NO_TEST_OBJECT )
892 				{
893 					// Make invisible
894 					pObject->fVisible = FALSE;
895 
896 					// JA25 CJC Oct 13 1999 - if node pointer is null don't try to set flags inside it!
897 					if( pObject->pNode )
898 					{
899 						pObject->pNode->uiFlags |= LEVELNODE_HIDDEN;
900 					}
901 
902 					pObject->fInWater = TRUE;
903 
904 					// Make ripple
905 					AniParams = ANITILE_PARAMS{};
906 					AniParams.sGridNo = sGridNo;
907 					AniParams.ubLevelID = ANI_STRUCT_LEVEL;
908 					AniParams.usTileIndex = THIRDMISS1;
909 					AniParams.sDelay = 50;
910 					AniParams.sStartFrame = 0;
911 					AniParams.uiFlags = ANITILE_FORWARD;
912 
913 
914 					if ( pObject->ubActionCode == THROW_ARM_ITEM )
915 					{
916 						AniParams.ubKeyFrame1 = 11;
917 						AniParams.uiKeyFrame1Code = ANI_KEYFRAME_CHAIN_WATER_EXPLOSION;
918 						AniParams.v.object        = pObject;
919 					}
920 
921 					pNode = CreateAnimationTile( &AniParams );
922 
923 					// Adjust for absolute positioning
924 					pNode->pLevelNode->uiFlags |= LEVELNODE_USEABSOLUTEPOS;
925 
926 					pNode->pLevelNode->sRelativeX = (INT16)pObject->Position.x;
927 					pNode->pLevelNode->sRelativeY = (INT16)pObject->Position.y;
928 					pNode->pLevelNode->sRelativeZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
929 				}
930 			}
931 
932 		}
933 		else if ( iCollisionCode == COLLISION_ROOF || iCollisionCode == COLLISION_INTERIOR_ROOF )
934 		{
935 			vTemp.x = 0;
936 			vTemp.y = 0;
937 			vTemp.z = -1;
938 
939 			pObject->fApplyFriction = TRUE;
940 			pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
941 
942 			dElasity = (float)1.4;
943 
944 			fDoCollision = TRUE;
945 
946 		}
947 		//else if ( iCollisionCode == COLLISION_INTERIOR_ROOF )
948 		//{
949 		//	vTemp.x = 0;
950 		//	vTemp.y = 0;
951 		//	vTemp.z = 1;
952 
953 		//	pObject->fApplyFriction = TRUE;
954 		//	pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
955 
956 		//	dElasity = (float)1.4;
957 
958 		//	fDoCollision = TRUE;
959 
960 		//}
961 		else if ( iCollisionCode == COLLISION_STRUCTURE_Z )
962 		{
963 			if ( CheckForCatcher( pObject, usStructureID ) )
964 			{
965 				return( FALSE );
966 			}
967 
968 			CheckForObjectHittingMerc( pObject, usStructureID );
969 
970 			vTemp.x = 0;
971 			vTemp.y = 0;
972 			vTemp.z = -1;
973 
974 			pObject->fApplyFriction = TRUE;
975 			pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
976 
977 			dElasity = (float)1.2;
978 
979 			fDoCollision = TRUE;
980 
981 		}
982 		else if (iCollisionCode == COLLISION_WALL_SOUTHEAST || iCollisionCode == COLLISION_WALL_SOUTHWEST ||
983 				iCollisionCode == COLLISION_WALL_NORTHEAST || iCollisionCode == COLLISION_WALL_NORTHWEST )
984 		{
985 			// A wall, do stuff
986 			vTemp.x = dNormalX;
987 			vTemp.y = dNormalY;
988 			vTemp.z = dNormalZ;
989 
990 			fDoCollision = TRUE;
991 
992 			dElasity = (float)1.1;
993 		}
994 		else
995 		{
996 			vector_3 vIncident;
997 
998 			if ( CheckForCatcher( pObject, usStructureID ) )
999 			{
1000 				return( FALSE );
1001 			}
1002 
1003 			CheckForObjectHittingMerc( pObject, usStructureID );
1004 
1005 			vIncident.x = dDeltaX;
1006 			vIncident.y = dDeltaY;
1007 			vIncident.z = 0;
1008 			// Nomralize
1009 
1010 			vIncident = VGetNormal( &vIncident );
1011 
1012 			//vTemp.x = -1;
1013 			//vTemp.y = 0;
1014 			//vTemp.z = 0;
1015 			vTemp.x = -1 * vIncident.x;
1016 			vTemp.y = -1 * vIncident.y;
1017 			vTemp.z = 0;
1018 
1019 			fDoCollision = TRUE;
1020 
1021 			dElasity = (float)1.1;
1022 		}
1023 
1024 		if ( fDoCollision )
1025 		{
1026 			pObject->CollisionNormal.x		= vTemp.x;
1027 			pObject->CollisionNormal.y		= vTemp.y;
1028 			pObject->CollisionNormal.z		= vTemp.z;
1029 			pObject->CollisionElasticity  = dElasity;
1030 			pObject->iOldCollisionCode  = iCollisionCode;
1031 
1032 			// Save collision velocity
1033 			pObject->CollisionVelocity = pObject->OldVelocity;
1034 
1035 			if ( pObject->fPotentialForDebug )
1036 			{
1037 				STLOGD("Object {}: Collision {}", REALOBJ2ID(pObject), iCollisionCode);
1038 				SLOGD(ST::format("Object {}: Collision Normal {} {} {}", REALOBJ2ID(pObject),
1039 					vTemp.x, vTemp.y, vTemp.z));
1040 				SLOGD(ST::format("Object {}: Collision OldPos {} {} {}", REALOBJ2ID(pObject),
1041 					pObject->Position.x, pObject->Position.y, pObject->Position.z));
1042 				SLOGD(ST::format("Object {}: Collision Velocity {} {} {}", REALOBJ2ID(pObject),
1043 					pObject->CollisionVelocity.x, pObject->CollisionVelocity.y, pObject->CollisionVelocity.z));
1044 			}
1045 		}
1046 		else
1047 		{
1048 			pObject->sConsecutiveCollisions = 0;
1049 			pObject->sConsecutiveZeroVelocityCollisions = 0;
1050 			pObject->fHaveHitGround = FALSE;
1051 		}
1052 	}
1053 
1054 	return( fDoCollision );
1055 }
1056 
1057 
PhysicsResolveCollision(REAL_OBJECT * pObject,vector_3 * pVelocity,vector_3 * pNormal,float CoefficientOfRestitution)1058 static void PhysicsResolveCollision(REAL_OBJECT* pObject, vector_3* pVelocity, vector_3* pNormal, float CoefficientOfRestitution)
1059 {
1060 	float ImpulseNumerator, Impulse;
1061 	vector_3 vTemp;
1062 
1063 	ImpulseNumerator = -1 * CoefficientOfRestitution * VDotProduct( pVelocity , pNormal );
1064 
1065 	Impulse = ImpulseNumerator;
1066 
1067 	vTemp = VMultScalar( pNormal, Impulse );
1068 
1069 	pObject->Velocity = VAdd( &(pObject->Velocity), &vTemp );
1070 
1071 }
1072 
1073 
1074 static BOOLEAN CheckForCatchObject(REAL_OBJECT* pObject);
1075 
1076 
PhysicsMoveObject(REAL_OBJECT * pObject)1077 static BOOLEAN PhysicsMoveObject(REAL_OBJECT* pObject)
1078 {
1079 	LEVELNODE *pNode;
1080 	INT16     sNewGridNo;
1081 
1082 	//Determine new gridno
1083 	sNewGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
1084 
1085 	if ( pObject->fFirstTimeMoved )
1086 	{
1087 		pObject->fFirstTimeMoved = FALSE;
1088 		pObject->sFirstGridNo    = sNewGridNo;
1089 	}
1090 
1091 	// CHECK FOR RANGE< IF INVALID, REMOVE!
1092 	if ( sNewGridNo == -1 )
1093 	{
1094 		PhysicsDeleteObject( pObject );
1095 		return( FALSE );
1096 	}
1097 
1098 	// Look at old gridno
1099 	if ( sNewGridNo != pObject->sGridNo || pObject->pNode == NULL )
1100 	{
1101 		if ( pObject->fVisible )
1102 		{
1103 			if ( CheckForCatchObject( pObject ) )
1104 			{
1105 				pObject->fVisible = FALSE;
1106 			}
1107 		}
1108 
1109 		if ( pObject->fVisible )
1110 		{
1111 			// Add smoke trails...
1112 			if ( pObject->Obj.usItem == MORTAR_SHELL && pObject->uiNumTilesMoved > 2 && pObject->ubActionCode == THROW_ARM_ITEM )
1113 			{
1114 				if ( sNewGridNo != pObject->sGridNo )
1115 				{
1116 					ANITILE_PARAMS	AniParams{};
1117 					AniParams.sGridNo = (INT16)sNewGridNo;
1118 					AniParams.ubLevelID = ANI_STRUCT_LEVEL;
1119 					AniParams.sDelay = (INT16)( 100 + PreRandom( 100 ) );
1120 					AniParams.sStartFrame = 0;
1121 					AniParams.uiFlags = ANITILE_FORWARD | ANITILE_ALWAYS_TRANSLUCENT;
1122 					AniParams.sX = (INT16)pObject->Position.x;
1123 					AniParams.sY = (INT16)pObject->Position.y;
1124 					AniParams.sZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
1125 					AniParams.zCachedFile = TILECACHEDIR "/msle_smk.sti";
1126 					CreateAnimationTile( &AniParams );
1127 				}
1128 			}
1129 			else if ( pObject->uiNumTilesMoved > 0 )
1130 			{
1131 				if ( sNewGridNo != pObject->sGridNo )
1132 				{
1133 					// We're at a new gridno!
1134 					if ( pObject->pNode != NULL )
1135 					{
1136 						RemoveStructFromLevelNode( pObject->sLevelNodeGridNo, pObject->pNode );
1137 					}
1138 
1139 					// We're at a new gridno!
1140 					if ( pObject->pShadow != NULL )
1141 					{
1142 						RemoveShadowFromLevelNode( pObject->sLevelNodeGridNo, pObject->pShadow );
1143 					}
1144 
1145 					// Now get graphic index
1146 					INT16 const sTileIndex = GetTileGraphicForItem(GCM->getItem(pObject->Obj.usItem));
1147 					//sTileIndex = BULLETTILE1;
1148 
1149 					// Set new gridno, add
1150 					pNode = AddStructToTail( sNewGridNo, sTileIndex );
1151 					pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
1152 					pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
1153 					pNode->uiFlags |= ( LEVELNODE_USEABSOLUTEPOS | LEVELNODE_IGNOREHEIGHT | LEVELNODE_PHYSICSOBJECT | LEVELNODE_DYNAMIC );
1154 
1155 					// Set levelnode
1156 					pObject->pNode = pNode;
1157 
1158 					// Add shadow
1159 					pNode = AddShadowToHead(sNewGridNo, sTileIndex);
1160 					pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
1161 					pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
1162 					pNode->uiFlags |= ( LEVELNODE_USEABSOLUTEPOS | LEVELNODE_IGNOREHEIGHT | LEVELNODE_PHYSICSOBJECT | LEVELNODE_DYNAMIC );
1163 
1164 					// Set levelnode
1165 					pObject->pShadow = pNode;
1166 
1167 					pObject->sLevelNodeGridNo = sNewGridNo;
1168 				}
1169 			}
1170 		}
1171 		else
1172 		{
1173 			// Remove!
1174 			if ( pObject->pNode != NULL )
1175 			{
1176 				RemoveStructFromLevelNode( pObject->sLevelNodeGridNo, pObject->pNode );
1177 			}
1178 
1179 			// We're at a new gridno!
1180 			if ( pObject->pShadow != NULL )
1181 			{
1182 				RemoveShadowFromLevelNode( pObject->sLevelNodeGridNo, pObject->pShadow );
1183 			}
1184 
1185 			pObject->pNode = NULL;
1186 			pObject->pShadow  = NULL;
1187 		}
1188 
1189 		if ( sNewGridNo != pObject->sGridNo )
1190 		{
1191 			pObject->uiNumTilesMoved++;
1192 		}
1193 
1194 		pObject->sGridNo = sNewGridNo;
1195 
1196 		if ( pObject->fPotentialForDebug )
1197 		{
1198 			STLOGD("Object {}d: uiNumTilesMoved: {}", REALOBJ2ID(pObject), pObject->uiNumTilesMoved);
1199 		}
1200 	}
1201 
1202 	if ( pObject->fVisible )
1203 	{
1204 		if ( pObject->Obj.usItem != MORTAR_SHELL || pObject->ubActionCode != THROW_ARM_ITEM )
1205 		{
1206 			if ( pObject->pNode != NULL )
1207 			{
1208 				// Add new object / update position
1209 				// Update position data
1210 				pObject->pNode->sRelativeX = (INT16)pObject->Position.x;
1211 				pObject->pNode->sRelativeY = (INT16)pObject->Position.y;
1212 				pObject->pNode->sRelativeZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
1213 
1214 				// Update position data
1215 				pObject->pShadow->sRelativeX = (INT16)pObject->Position.x;
1216 				pObject->pShadow->sRelativeY = (INT16)pObject->Position.y;
1217 				pObject->pShadow->sRelativeZ = (INT16)gpWorldLevelData[ pObject->sGridNo ].sHeight;
1218 			}
1219 		}
1220 	}
1221 
1222 	return( TRUE );
1223 }
1224 
1225 
1226 static FLOAT CalculateObjectTrajectory(INT16 sTargetZ, const OBJECTTYPE* pItem, vector_3* vPosition, vector_3* vForce, INT16* psFinalGridNo);
1227 
1228 
FindBestForceForTrajectory(INT16 sSrcGridNo,INT16 sGridNo,INT16 sStartZ,INT16 sEndZ,float dzDegrees,const OBJECTTYPE * pItem,INT16 * psGridNo,float * pdMagForce)1229 static vector_3 FindBestForceForTrajectory(INT16 sSrcGridNo, INT16 sGridNo, INT16 sStartZ, INT16 sEndZ, float dzDegrees, const OBJECTTYPE* pItem, INT16* psGridNo, float* pdMagForce)
1230 {
1231 	vector_3 vDirNormal, vPosition, vForce;
1232 	INT16    sDestX, sDestY, sSrcX, sSrcY;
1233 	float    dForce = 20;
1234 	float    dRange;
1235 	float    dPercentDiff = 0;
1236 	float    dTestRange, dTestDiff;
1237 	INT32    iNumChecks = 0;
1238 
1239 	// Get XY from gridno
1240 	ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
1241 	ConvertGridNoToCenterCellXY( sSrcGridNo, &sSrcX, &sSrcY );
1242 
1243 	// Set position
1244 	vPosition.x = sSrcX;
1245 	vPosition.y = sSrcY;
1246 	vPosition.z = sStartZ;
1247 
1248 	// OK, get direction normal
1249 	vDirNormal.x = (float)(sDestX - sSrcX);
1250 	vDirNormal.y = (float)(sDestY - sSrcY);
1251 	vDirNormal.z = 0;
1252 
1253 	// NOmralize
1254 	vDirNormal = VGetNormal( &vDirNormal );
1255 
1256 	// From degrees, calculate Z portion of normal
1257 	vDirNormal.z = (float)sin( dzDegrees );
1258 
1259 	// Get range
1260 	dRange = (float)GetRangeInCellCoordsFromGridNoDiff( sGridNo, sSrcGridNo );
1261 
1262 	//calculate force needed
1263 	{
1264 		dForce = (float)( 12 * ( sqrt( ( GRAVITY * dRange ) / sin( 2 * dzDegrees ) ) ) );
1265 	}
1266 
1267 	do
1268 	{
1269 		// This first force is just an estimate...
1270 		// now di a binary search to find best value....
1271 		iNumChecks++;
1272 
1273 
1274 		// Now use a force
1275 		vForce.x = dForce * vDirNormal.x;
1276 		vForce.y = dForce * vDirNormal.y;
1277 		vForce.z = dForce * vDirNormal.z;
1278 
1279 		dTestRange = CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
1280 
1281 		// What's the diff?
1282 		dTestDiff = dTestRange - dRange;
1283 
1284 		// How have we done?
1285 		// < 5% off...
1286 		if ( fabs( ( dTestDiff / dRange ) ) < .01 )
1287 		{
1288 			break;
1289 		}
1290 
1291 		if ( iNumChecks > MAX_INTEGRATIONS )
1292 		{
1293 			break;
1294 		}
1295 
1296 		// What is the Percentage difference?
1297 		dPercentDiff = dForce * ( dTestDiff / dRange );
1298 
1299 		// Adjust force accordingly
1300 		dForce = dForce - ( ( dPercentDiff ) / 2 );
1301 
1302 	} while( TRUE );
1303 
1304 	// OK, we have our force, calculate change to get through without collide
1305 	//if ( ChanceToGetThroughObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, NULL ) == 0 )
1306 	{
1307 		//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Chance to get through throw is 0." );
1308 	}
1309 
1310 	if ( pdMagForce )
1311 	{
1312 		(*pdMagForce) = dForce;
1313 	}
1314 	STLOGD("Number of integration: {}", iNumChecks);
1315 
1316 	return( vForce );
1317 }
1318 
1319 
FindBestAngleForTrajectory(INT16 sSrcGridNo,INT16 sGridNo,INT16 sStartZ,INT16 sEndZ,float dForce,const OBJECTTYPE * pItem,INT16 * psGridNo)1320 static float FindBestAngleForTrajectory(INT16 sSrcGridNo, INT16 sGridNo, INT16 sStartZ, INT16 sEndZ, float dForce, const OBJECTTYPE* pItem, INT16* psGridNo)
1321 {
1322 	vector_3 vDirNormal, vPosition, vForce;
1323 	INT16    sDestX, sDestY, sSrcX, sSrcY;
1324 	float    dRange;
1325 	float    dzDegrees = ( (float)PI/8 );
1326 	float    dPercentDiff = 0;
1327 	float    dTestRange, dTestDiff;
1328 	INT32    iNumChecks = 0;
1329 
1330 
1331 	// Get XY from gridno
1332 	ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
1333 	ConvertGridNoToCenterCellXY( sSrcGridNo, &sSrcX, &sSrcY );
1334 
1335 	// Set position
1336 	vPosition.x = sSrcX;
1337 	vPosition.y = sSrcY;
1338 	vPosition.z = sStartZ;
1339 
1340 	// OK, get direction normal
1341 	vDirNormal.x = (float)(sDestX - sSrcX);
1342 	vDirNormal.y = (float)(sDestY - sSrcY);
1343 	vDirNormal.z = 0;
1344 
1345 	// NOmralize
1346 	vDirNormal = VGetNormal( &vDirNormal );
1347 
1348 	// From degrees, calculate Z portion of normal
1349 	vDirNormal.z = (float)sin( dzDegrees );
1350 
1351 	// Get range
1352 	dRange = (float)GetRangeInCellCoordsFromGridNoDiff( sGridNo, sSrcGridNo );
1353 
1354 	do
1355 	{
1356 		// This first direction is just an estimate...
1357 		// now do a binary search to find best value....
1358 		iNumChecks++;
1359 
1360 		// Now use a force
1361 		vForce.x = dForce * vDirNormal.x;
1362 		vForce.y = dForce * vDirNormal.y;
1363 		vForce.z = dForce * vDirNormal.z;
1364 
1365 		dTestRange = CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
1366 
1367 		// What's the diff?
1368 		dTestDiff = dTestRange - dRange;
1369 
1370 		// How have we done?
1371 		// < 5% off...
1372 		if ( fabs( (FLOAT)( dTestDiff / dRange ) ) < .05 )
1373 		{
1374 			break;
1375 		}
1376 
1377 		if ( iNumChecks > MAX_INTEGRATIONS )
1378 		{
1379 			break;
1380 		}
1381 
1382 		// What is the Percentage difference?
1383 		dPercentDiff = dzDegrees * ( dTestDiff / dRange );
1384 
1385 		// Adjust degrees accordingly
1386 		dzDegrees = dzDegrees - ( dPercentDiff / 2 );
1387 
1388 		// OK, If our angle is too far either way, giveup!
1389 		if ( fabs( dzDegrees ) >= ( PI / 2 ) || fabs( dzDegrees ) <= 0.005 )
1390 		{
1391 			// Use 0.....
1392 			dzDegrees = 0;
1393 			// From degrees, calculate Z portion of normal
1394 			vDirNormal.z	= (float)sin( dzDegrees );
1395 			// Now use a force
1396 			vForce.x = dForce * vDirNormal.x;
1397 			vForce.y = dForce * vDirNormal.y;
1398 			vForce.z = dForce * vDirNormal.z;
1399 			dTestRange = CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
1400 			return( (FLOAT)( dzDegrees ) );
1401 		}
1402 
1403 
1404 		// From degrees, calculate Z portion of normal
1405 		vDirNormal.z = (float)sin( dzDegrees );
1406 
1407 	} while( TRUE );
1408 
1409 	// OK, we have our force, calculate change to get through without collide
1410 	//if ( ChanceToGetThroughObjectTrajectory( sEndZ, pItem, &vPosition, &vForce ) == 0 )
1411 	//{
1412 	//	ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Chance to get through throw is 0." );
1413 	//}
1414 
1415 	return( dzDegrees );
1416 }
1417 
1418 
FindTrajectory(INT16 sSrcGridNo,INT16 sGridNo,INT16 sStartZ,INT16 sEndZ,float dForce,float dzDegrees,const OBJECTTYPE * pItem,INT16 * psGridNo)1419 static void FindTrajectory(INT16 sSrcGridNo, INT16 sGridNo, INT16 sStartZ, INT16 sEndZ, float dForce, float dzDegrees, const OBJECTTYPE* pItem, INT16* psGridNo)
1420 {
1421 	vector_3 vDirNormal, vPosition, vForce;
1422 	INT16    sDestX, sDestY, sSrcX, sSrcY;
1423 
1424 	// Get XY from gridno
1425 	ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
1426 	ConvertGridNoToCenterCellXY( sSrcGridNo, &sSrcX, &sSrcY );
1427 
1428 	// Set position
1429 	vPosition.x = sSrcX;
1430 	vPosition.y = sSrcY;
1431 	vPosition.z = sStartZ;
1432 
1433 	// OK, get direction normal
1434 	vDirNormal.x = (float)(sDestX - sSrcX);
1435 	vDirNormal.y = (float)(sDestY - sSrcY);
1436 	vDirNormal.z = 0;
1437 
1438 	// NOmralize
1439 	vDirNormal = VGetNormal( &vDirNormal );
1440 
1441 	// From degrees, calculate Z portion of normal
1442 	vDirNormal.z = (float)sin( dzDegrees );
1443 
1444 	// Now use a force
1445 	vForce.x = dForce * vDirNormal.x;
1446 	vForce.y = dForce * vDirNormal.y;
1447 	vForce.z = dForce * vDirNormal.z;
1448 
1449 	CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
1450 }
1451 
1452 
1453 
1454 // OK, this will, given a target Z, INVTYPE, source, target gridnos, initial force vector, will
1455 // return range
CalculateObjectTrajectory(INT16 sTargetZ,const OBJECTTYPE * pItem,vector_3 * vPosition,vector_3 * vForce,INT16 * psFinalGridNo)1456 static FLOAT CalculateObjectTrajectory(INT16 sTargetZ, const OBJECTTYPE* pItem, vector_3* vPosition, vector_3* vForce, INT16* psFinalGridNo)
1457 {
1458 	FLOAT dDiffX, dDiffY;
1459 	INT16 sGridNo;
1460 
1461 	if ( psFinalGridNo )
1462 	{
1463 		(*psFinalGridNo) = NOWHERE;
1464 	}
1465 
1466 	REAL_OBJECT* const pObject = CreatePhysicalObject(pItem, -1, vPosition->x, vPosition->y, vPosition->z, vForce->x, vForce->y, vForce->z, NULL, NO_THROW_ACTION, 0);
1467 
1468 	// Set some special values...
1469 	pObject->fTestObject = TEST_OBJECT_NO_COLLISIONS;
1470 	pObject->TestZTarget = sTargetZ;
1471 	pObject->fTestPositionNotSet = TRUE;
1472 	pObject->fVisible = FALSE;
1473 
1474 	// Alrighty, move this beast until it dies....
1475 	while( pObject->fAlive )
1476 	{
1477 		SimulateObject( pObject, (float)DELTA_T );
1478 	}
1479 
1480 	// Calculate gridno from last position
1481 	sGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
1482 
1483 	PhysicsDeleteObject( pObject );
1484 
1485 	// get new x, y, z values
1486 	dDiffX = ( pObject->TestTargetPosition.x - vPosition->x );
1487 	dDiffY = ( pObject->TestTargetPosition.y - vPosition->y );
1488 
1489 	if ( psFinalGridNo )
1490 	{
1491 		(*psFinalGridNo) = sGridNo;
1492 	}
1493 
1494 	return( (FLOAT)sqrt( ( dDiffX * dDiffX ) + ( dDiffY * dDiffY ) ) );
1495 
1496 }
1497 
1498 
ChanceToGetThroughObjectTrajectory(INT16 sTargetZ,const OBJECTTYPE * pItem,vector_3 * vPosition,vector_3 * vForce,INT16 * psNewGridNo,INT8 * pbLevel,BOOLEAN fFromUI)1499 static INT32 ChanceToGetThroughObjectTrajectory(INT16 sTargetZ, const OBJECTTYPE* pItem, vector_3* vPosition, vector_3* vForce, INT16* psNewGridNo, INT8* pbLevel, BOOLEAN fFromUI)
1500 {
1501 	REAL_OBJECT* const pObject = CreatePhysicalObject(pItem, -1, vPosition->x, vPosition->y, vPosition->z, vForce->x, vForce->y, vForce->z, NULL, NO_THROW_ACTION, 0);
1502 
1503 	// Set some special values...
1504 	pObject->fTestObject = TEST_OBJECT_NOTWALLROOF_COLLISIONS;
1505 	pObject->fTestPositionNotSet = TRUE;
1506 	pObject->TestZTarget = sTargetZ;
1507 	pObject->fVisible = FALSE;
1508 	//pObject->fPotentialForDebug = TRUE;
1509 
1510 	// Alrighty, move this beast until it dies....
1511 	while( pObject->fAlive )
1512 	{
1513 		SimulateObject( pObject, (float)DELTA_T );
1514 	}
1515 
1516 
1517 	if ( psNewGridNo != NULL )
1518 	{
1519 		// Calculate gridno from last position
1520 
1521 		// If NOT from UI, use exact collision position
1522 		if ( fFromUI )
1523 		{
1524 			(*psNewGridNo) = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
1525 		}
1526 		else
1527 		{
1528 			(*psNewGridNo) = MAPROWCOLTOPOS( ( (INT16)pObject->EndedWithCollisionPosition.y / CELL_Y_SIZE ), ( (INT16)pObject->EndedWithCollisionPosition.x / CELL_X_SIZE ) );
1529 		}
1530 
1531 		(*pbLevel) = GET_OBJECT_LEVEL( pObject->EndedWithCollisionPosition.z - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ (*psNewGridNo) ].sHeight ) );
1532 	}
1533 
1534 	PhysicsDeleteObject( pObject );
1535 
1536 	// See If we collided
1537 	if ( pObject->fTestEndedWithCollision )
1538 	{
1539 		return( 0 );
1540 	}
1541 	return( 100 );
1542 }
1543 
1544 
1545 static FLOAT CalculateForceFromRange(INT16 sRange, FLOAT dDegrees);
1546 static FLOAT CalculateSoldierMaxForce(const SOLDIERTYPE* pSoldier, FLOAT dDegrees, const OBJECTTYPE* pItem, BOOLEAN fArmed);
1547 
1548 
CalculateLaunchItemBasicParams(const SOLDIERTYPE * pSoldier,const OBJECTTYPE * pItem,INT16 sGridNo,UINT8 ubLevel,INT16 sEndZ,FLOAT * pdMagForce,FLOAT * pdDegrees,INT16 * psFinalGridNo,BOOLEAN fArmed)1549 static void CalculateLaunchItemBasicParams(const SOLDIERTYPE* pSoldier, const OBJECTTYPE* pItem, INT16 sGridNo, UINT8 ubLevel, INT16 sEndZ,  FLOAT* pdMagForce, FLOAT* pdDegrees, INT16* psFinalGridNo, BOOLEAN fArmed)
1550 {
1551 	INT16   sInterGridNo;
1552 	INT16   sStartZ;
1553 	FLOAT   dMagForce, dMaxForce, dMinForce;
1554 	FLOAT   dDegrees, dNewDegrees;
1555 	BOOLEAN fThroughIntermediateGridNo = FALSE;
1556 	UINT16  usLauncher;
1557 	BOOLEAN fIndoors = FALSE;
1558 	BOOLEAN fLauncher = FALSE;
1559 	BOOLEAN fMortar = FALSE;
1560 	BOOLEAN fGLauncher = FALSE;
1561 	INT16   sMinRange = 0;
1562 
1563 	// Start with default degrees/ force
1564 	dDegrees = OUTDOORS_START_ANGLE;
1565 	sStartZ  = GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel );
1566 
1567 	// Are we armed, and are we throwing a LAUNCHABLE?
1568 
1569 	usLauncher = GetLauncherFromLaunchable( pItem->usItem );
1570 
1571 	if ( fArmed && ( usLauncher == MORTAR || pItem->usItem == MORTAR ) )
1572 	{
1573 		// Start at 0....
1574 		sStartZ = ( pSoldier->bLevel * 256 );
1575 		fMortar = TRUE;
1576 		sMinRange = MIN_MORTAR_RANGE;
1577 		//fLauncher = TRUE;
1578 	}
1579 
1580 	if ( fArmed && ( usLauncher == GLAUNCHER || usLauncher == UNDER_GLAUNCHER || pItem->usItem == GLAUNCHER || pItem->usItem == UNDER_GLAUNCHER ) )
1581 	{
1582 		// OK, look at target level and decide angle to use...
1583 		if ( ubLevel == 1 )
1584 		{
1585 			//dDegrees  = GLAUNCHER_START_ANGLE;
1586 			dDegrees  = GLAUNCHER_HIGHER_LEVEL_START_ANGLE;
1587 		}
1588 		else
1589 		{
1590 			dDegrees  = GLAUNCHER_START_ANGLE;
1591 		}
1592 		fGLauncher = TRUE;
1593 		sMinRange  = MIN_MORTAR_RANGE;
1594 		//fLauncher = TRUE;
1595 	}
1596 
1597 	// CHANGE DEGREE VALUES BASED ON IF WE ARE INSIDE, ETC
1598 	// ARE WE INSIDE?
1599 
1600 	if ( gfCaves || gfBasement  )
1601 	{
1602 		// Adjust angle....
1603 		dDegrees = INDOORS_START_ANGLE;
1604 		fIndoors = TRUE;
1605 	}
1606 
1607 	if ( ( IsRoofPresentAtGridno( pSoldier->sGridNo ) ) && pSoldier->bLevel == 0 )
1608 	{
1609 		// Adjust angle....
1610 		dDegrees = INDOORS_START_ANGLE;
1611 		fIndoors = TRUE;
1612 	}
1613 
1614 	// IS OUR TARGET INSIDE?
1615 	if ( IsRoofPresentAtGridno( sGridNo ) && ubLevel == 0 )
1616 	{
1617 		// Adjust angle....
1618 		dDegrees = INDOORS_START_ANGLE;
1619 		fIndoors = TRUE;
1620 	}
1621 
1622 
1623 	// OK, look if we can go through a windows here...
1624 	if ( ubLevel == 0 )
1625 	{
1626 		sInterGridNo = SoldierToLocationWindowTest( pSoldier, sGridNo );
1627 	}
1628 	else
1629 	{
1630 		sInterGridNo = NOWHERE;
1631 	}
1632 
1633 	if ( sInterGridNo != NOWHERE )
1634 	{
1635 		// IF so, adjust target height, gridno....
1636 		SLOGD("Through a window!" );
1637 
1638 		fThroughIntermediateGridNo = TRUE;
1639 	}
1640 
1641 	if ( !fLauncher )
1642 	{
1643 		// Find force for basic
1644 		FindBestForceForTrajectory( pSoldier->sGridNo, sGridNo, sStartZ, sEndZ, dDegrees, pItem, psFinalGridNo, &dMagForce );
1645 
1646 		// Adjust due to max range....
1647 		dMaxForce   = CalculateSoldierMaxForce( pSoldier, dDegrees, pItem, fArmed );
1648 
1649 		if ( fIndoors )
1650 		{
1651 			dMaxForce = dMaxForce * 2;
1652 		}
1653 
1654 		if ( dMagForce > dMaxForce )
1655 		{
1656 			dMagForce = dMaxForce;
1657 		}
1658 
1659 		// ATE: If we are a mortar, make sure we are at min.
1660 		if ( fMortar || fGLauncher )
1661 		{
1662 			// find min force
1663 			dMinForce = CalculateForceFromRange( (INT16)( sMinRange / 10 ), (FLOAT)( PI / 4 ) );
1664 
1665 			if ( dMagForce < dMinForce )
1666 			{
1667 				dMagForce = dMinForce;
1668 			}
1669 		}
1670 
1671 		if ( fThroughIntermediateGridNo )
1672 		{
1673 			// Given this power, now try and go through this window....
1674 			dDegrees = FindBestAngleForTrajectory( pSoldier->sGridNo, sInterGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel ), 150, dMagForce, pItem, psFinalGridNo );
1675 		}
1676 	}
1677 	else
1678 	{
1679 		// Use MAX force, vary angle....
1680 		dMagForce   = CalculateSoldierMaxForce( pSoldier, dDegrees, pItem, fArmed );
1681 
1682 		if ( ubLevel == 0 )
1683 		{
1684 			dMagForce = (float)( dMagForce * 1.25 );
1685 		}
1686 
1687 		FindTrajectory( pSoldier->sGridNo, sGridNo, sStartZ, sEndZ, dMagForce, dDegrees, pItem, psFinalGridNo );
1688 
1689 		if ( ubLevel == 1 && !fThroughIntermediateGridNo )
1690 		{
1691 			// Is there a guy here...?
1692 			if (WhoIsThere2(sGridNo, ubLevel) != NULL)
1693 			{
1694 				dMagForce = (float)( dMagForce * 0.85 );
1695 
1696 				// Yep, try to get angle...
1697 				dNewDegrees = FindBestAngleForTrajectory( pSoldier->sGridNo, sGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel ), 150, dMagForce, pItem, psFinalGridNo );
1698 
1699 				if ( dNewDegrees != 0 )
1700 				{
1701 					dDegrees = dNewDegrees;
1702 				}
1703 			}
1704 		}
1705 
1706 		if ( fThroughIntermediateGridNo )
1707 		{
1708 			dDegrees = FindBestAngleForTrajectory( pSoldier->sGridNo, sInterGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel ), 150, dMagForce, pItem, psFinalGridNo );
1709 		}
1710 	}
1711 
1712 
1713 	(*pdMagForce) = dMagForce;
1714 	(*pdDegrees ) = dDegrees;
1715 }
1716 
1717 
CalculateLaunchItemChanceToGetThrough(const SOLDIERTYPE * pSoldier,const OBJECTTYPE * pItem,INT16 sGridNo,UINT8 ubLevel,INT16 sEndZ,INT16 * psFinalGridNo,BOOLEAN fArmed,INT8 * pbLevel,BOOLEAN fFromUI)1718 BOOLEAN CalculateLaunchItemChanceToGetThrough(const SOLDIERTYPE* pSoldier, const OBJECTTYPE* pItem, INT16 sGridNo, UINT8 ubLevel, INT16 sEndZ, INT16* psFinalGridNo, BOOLEAN fArmed, INT8* pbLevel, BOOLEAN fFromUI)
1719 {
1720 	FLOAT    dForce, dDegrees;
1721 	INT16    sDestX, sDestY, sSrcX, sSrcY;
1722 	vector_3 vForce, vPosition, vDirNormal;
1723 
1724 	/* Prevent throwing to the same tile the thrower is standing on and only the
1725 	 * target level differs.  This would lead to an endless loop when calculation
1726 	 * the trajectory. */
1727 	if (pSoldier->sGridNo == sGridNo)
1728 	{
1729 		*psFinalGridNo = sGridNo;
1730 		*pbLevel       = pSoldier->bLevel;
1731 		return FALSE;
1732 	}
1733 
1734 	// Ge7t basic launch params...
1735 	CalculateLaunchItemBasicParams( pSoldier, pItem, sGridNo, ubLevel, sEndZ, &dForce, &dDegrees, psFinalGridNo, fArmed );
1736 
1737 	// Get XY from gridno
1738 	ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
1739 	ConvertGridNoToCenterCellXY( pSoldier->sGridNo, &sSrcX, &sSrcY );
1740 
1741 	// Set position
1742 	vPosition.x = sSrcX;
1743 	vPosition.y = sSrcY;
1744 	vPosition.z = GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel );
1745 
1746 	// OK, get direction normal
1747 	vDirNormal.x = (float)(sDestX - sSrcX);
1748 	vDirNormal.y = (float)(sDestY - sSrcY);
1749 	vDirNormal.z = 0;
1750 
1751 	// NOmralize
1752 	vDirNormal = VGetNormal( &vDirNormal );
1753 
1754 	// From degrees, calculate Z portion of normal
1755 	vDirNormal.z = (float)sin( dDegrees );
1756 
1757 	// Do force....
1758 	vForce.x = dForce * vDirNormal.x;
1759 	vForce.y = dForce * vDirNormal.y;
1760 	vForce.z = dForce * vDirNormal.z;
1761 
1762 	// OK, we have our force, calculate change to get through without collide
1763 	if ( ChanceToGetThroughObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psFinalGridNo, pbLevel, fFromUI ) == 0 )
1764 	{
1765 		return( FALSE );
1766 	}
1767 
1768 	if ( (*pbLevel) != ubLevel )
1769 	{
1770 		return( FALSE );
1771 	}
1772 
1773 	if ( !fFromUI && (*psFinalGridNo) != sGridNo )
1774 	{
1775 		return( FALSE );
1776 	}
1777 
1778 	return( TRUE );
1779 }
1780 
1781 
CalculateForceFromRange(INT16 sRange,FLOAT dDegrees)1782 static FLOAT CalculateForceFromRange(INT16 sRange, FLOAT dDegrees)
1783 {
1784 	FLOAT      dMagForce;
1785 	INT16      sSrcGridNo, sDestGridNo;
1786 	OBJECTTYPE Object;
1787 	INT16      sFinalGridNo;
1788 
1789 	// OK, use a fake gridno, find the new gridno based on range, use height of merc, end height of ground,
1790 	// 45 degrees
1791 	sSrcGridNo  = 4408;
1792 	sDestGridNo = 4408 + ( sRange * WORLD_COLS );
1793 
1794 	// Use a grenade objecttype
1795 	CreateItem( HAND_GRENADE, 100, &Object );
1796 
1797 	FindBestForceForTrajectory( sSrcGridNo, sDestGridNo, GET_SOLDIER_THROW_HEIGHT( 0 ), 0, dDegrees, &Object, &sFinalGridNo, &dMagForce );
1798 
1799 	return( dMagForce );
1800 }
1801 
1802 
CalculateSoldierMaxForce(const SOLDIERTYPE * pSoldier,FLOAT dDegrees,const OBJECTTYPE * pItem,BOOLEAN fArmed)1803 static FLOAT CalculateSoldierMaxForce(const SOLDIERTYPE* pSoldier, FLOAT dDegrees, const OBJECTTYPE* pItem, BOOLEAN fArmed)
1804 {
1805 	INT32 uiMaxRange;
1806 	FLOAT dMagForce;
1807 
1808 	dDegrees = (FLOAT)( PI/4 );
1809 
1810 	uiMaxRange = CalcMaxTossRange( pSoldier, pItem->usItem, fArmed );
1811 
1812 	dMagForce = CalculateForceFromRange( (INT16) uiMaxRange, dDegrees );
1813 
1814 	return( dMagForce );
1815 }
1816 
1817 
1818 #define MAX_MISS_BY	30
1819 #define MIN_MISS_BY	1
1820 #define MAX_MISS_RADIUS	5
1821 
1822 
1823 static UINT16 RandomGridFromRadius(INT16 sSweetGridNo, INT8 ubMinRadius, INT8 ubMaxRadius);
1824 
1825 
CalculateLaunchItemParamsForThrow(SOLDIERTYPE * const pSoldier,INT16 sGridNo,const UINT8 ubLevel,const INT16 sEndZ,OBJECTTYPE * const pItem,INT8 bMissBy,const UINT8 ubActionCode,SOLDIERTYPE * const target)1826 void CalculateLaunchItemParamsForThrow(SOLDIERTYPE* const pSoldier, INT16 sGridNo, const UINT8 ubLevel, const INT16 sEndZ, OBJECTTYPE* const pItem, INT8 bMissBy, const UINT8 ubActionCode, SOLDIERTYPE* const target)
1827 {
1828 	FLOAT    dForce, dDegrees;
1829 	INT16    sDestX, sDestY, sSrcX, sSrcY;
1830 	vector_3 vForce, vDirNormal;
1831 	INT16    sFinalGridNo;
1832 	BOOLEAN  fArmed = FALSE;
1833 	UINT16   usLauncher;
1834 	INT16    sStartZ;
1835 	INT8     bMinMissRadius, bMaxMissRadius, bMaxRadius;
1836 	FLOAT    fScale;
1837 
1838 	// Set target if anyone
1839 	pSoldier->target = WhoIsThere2(sGridNo, ubLevel);
1840 
1841 	if ( ubActionCode == THROW_ARM_ITEM )
1842 	{
1843 		fArmed = TRUE;
1844 	}
1845 
1846 	if ( bMissBy < 0 )
1847 	{
1848 		// then we hit!
1849 		bMissBy = 0;
1850 	}
1851 
1852 	//if ( 0 )
1853 	if ( bMissBy > 0 )
1854 	{
1855 		// Max the miss variance
1856 		if ( bMissBy > MAX_MISS_BY )
1857 		{
1858 			bMissBy = MAX_MISS_BY;
1859 		}
1860 
1861 		// Min the miss varience...
1862 		if ( bMissBy < MIN_MISS_BY )
1863 		{
1864 			bMissBy = MIN_MISS_BY;
1865 		}
1866 
1867 		// Adjust position, force, angle
1868 		SLOGD("Throw miss by: %d", bMissBy );
1869 
1870 		// Default to max radius...
1871 		bMaxRadius = 5;
1872 
1873 		// scale if pyth spaces away is too far
1874 		if ( PythSpacesAway( sGridNo, pSoldier->sGridNo ) < ( (float)bMaxRadius / (float)1.5 ) )
1875 		{
1876 			bMaxRadius = PythSpacesAway( sGridNo, pSoldier->sGridNo ) / 2;
1877 		}
1878 
1879 
1880 		// Get radius
1881 		fScale = ( (float)bMissBy / (float) MAX_MISS_BY );
1882 
1883 		bMaxMissRadius = (INT8)( bMaxRadius * fScale );
1884 
1885 		// Limit max radius...
1886 		if ( bMaxMissRadius > 4 )
1887 		{
1888 			bMaxMissRadius = 4;
1889 		}
1890 
1891 
1892 		bMinMissRadius = bMaxMissRadius - 1;
1893 
1894 		if ( bMinMissRadius < 2 )
1895 		{
1896 			bMinMissRadius = 2;
1897 		}
1898 
1899 		if ( bMaxMissRadius < bMinMissRadius )
1900 		{
1901 			bMaxMissRadius = bMinMissRadius;
1902 		}
1903 
1904 		sGridNo = RandomGridFromRadius( sGridNo, bMinMissRadius, bMaxMissRadius );
1905 	}
1906 
1907 	// Get basic launch params...
1908 	CalculateLaunchItemBasicParams( pSoldier, pItem, sGridNo, ubLevel, sEndZ, &dForce, &dDegrees, &sFinalGridNo, fArmed );
1909 
1910 	// Get XY from gridno
1911 	ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
1912 	ConvertGridNoToCenterCellXY( pSoldier->sGridNo, &sSrcX, &sSrcY );
1913 
1914 	// OK, get direction normal
1915 	vDirNormal.x = (float)(sDestX - sSrcX);
1916 	vDirNormal.y = (float)(sDestY - sSrcY);
1917 	vDirNormal.z = 0;
1918 
1919 	// NOmralize
1920 	vDirNormal = VGetNormal( &vDirNormal );
1921 
1922 	// From degrees, calculate Z portion of normal
1923 	vDirNormal.z = (float)sin( dDegrees );
1924 
1925 	// Do force....
1926 	vForce.x = dForce * vDirNormal.x;
1927 	vForce.y = dForce * vDirNormal.y;
1928 	vForce.z = dForce * vDirNormal.z;
1929 
1930 
1931 	// Allocate Throw Parameters
1932 	pSoldier->pThrowParams = new THROW_PARAMS{};
1933 
1934 	pSoldier->pTempObject  = new OBJECTTYPE{};
1935 
1936 	*pSoldier->pTempObject = *pItem;
1937 	pSoldier->pThrowParams->dX = (float)sSrcX;
1938 	pSoldier->pThrowParams->dY = (float)sSrcY;
1939 
1940 
1941 	sStartZ = GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel );
1942 	usLauncher = GetLauncherFromLaunchable( pItem->usItem );
1943 	if ( fArmed && usLauncher == MORTAR )
1944 	{
1945 		// Start at 0....
1946 		sStartZ = ( pSoldier->bLevel * 256 ) + 50;
1947 	}
1948 
1949 	pSoldier->pThrowParams->dZ = (float)sStartZ;
1950 	pSoldier->pThrowParams->dForceX = vForce.x;
1951 	pSoldier->pThrowParams->dForceY = vForce.y;
1952 	pSoldier->pThrowParams->dForceZ = vForce.z;
1953 	pSoldier->pThrowParams->dLifeSpan = -1;
1954 	pSoldier->pThrowParams->ubActionCode = ubActionCode;
1955 	pSoldier->pThrowParams->target       = target;
1956 
1957 	// Dirty interface
1958 	DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
1959 
1960 }
1961 
1962 
1963 static BOOLEAN DoCatchObject(REAL_OBJECT* pObject);
1964 
1965 
CheckForCatcher(REAL_OBJECT * const o,UINT16 const structure_id)1966 static BOOLEAN CheckForCatcher(REAL_OBJECT* const o, UINT16 const structure_id)
1967 {
1968 	// Do we want to catch?
1969 	if (o->fTestObject  != NO_TEST_OBJECT)          return FALSE;
1970 	if (o->ubActionCode != THROW_TARGET_MERC_CATCH) return FALSE;
1971 	// Is it a guy?
1972 	if (structure_id    >= INVALID_STRUCTURE_ID)    return FALSE;
1973 	// Is it the same guy?
1974 	if (o->target       != &GetMan(structure_id))   return FALSE;
1975 	if (!DoCatchObject(o))                          return FALSE;
1976 
1977 	o->fAlive = FALSE;
1978 	return TRUE;
1979 }
1980 
1981 
CheckForObjectHittingMerc(REAL_OBJECT * const o,UINT16 const structure_id)1982 static void CheckForObjectHittingMerc(REAL_OBJECT* const o, UINT16 const structure_id)
1983 {
1984 	// Do we want to catch?
1985 	if (o->fTestObject != NO_TEST_OBJECT)             return;
1986 	// Is it a guy?
1987 	if (structure_id   >= INVALID_STRUCTURE_ID)       return;
1988 	if (structure_id   == o->ubLastTargetTakenDamage) return;
1989 
1990 	SOLDIERTYPE& s      = GetMan(structure_id);
1991 	INT16 const  damage = 1;
1992 	INT16 const  breath = 0;
1993 	EVENT_SoldierGotHit(&s, NOTHING, damage, breath, s.bDirection, 0, o->owner, FIRE_WEAPON_TOSSED_OBJECT_SPECIAL, 0, NOWHERE);
1994 
1995 	o->ubLastTargetTakenDamage = structure_id;
1996 }
1997 
1998 
1999 static BOOLEAN AttemptToCatchObject(REAL_OBJECT* pObject);
2000 
2001 
CheckForCatchObject(REAL_OBJECT * pObject)2002 static BOOLEAN CheckForCatchObject(REAL_OBJECT* pObject)
2003 {
2004 	UINT32 uiSpacesAway;
2005 
2006 	// Do we want to catch?
2007 	if ( pObject->fTestObject ==  NO_TEST_OBJECT )
2008 	{
2009 		if ( pObject->ubActionCode == THROW_TARGET_MERC_CATCH )
2010 		{
2011 			SOLDIERTYPE* const pSoldier = pObject->target;
2012 
2013 			// Is it a guy?
2014 			// Are we close to this guy?
2015 			uiSpacesAway = PythSpacesAway( pObject->sGridNo, pSoldier->sGridNo );
2016 
2017 			if ( uiSpacesAway < 4 && !pObject->fAttemptedCatch )
2018 			{
2019 				if ( pSoldier->usAnimState != CATCH_STANDING &&
2020 					pSoldier->usAnimState != CATCH_CROUCHED &&
2021 					pSoldier->usAnimState != LOWER_RIFLE )
2022 				{
2023 					if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND )
2024 					{
2025 						EVENT_InitNewSoldierAnim( pSoldier, CATCH_STANDING, 0 , FALSE );
2026 					}
2027 					else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_CROUCH )
2028 					{
2029 						EVENT_InitNewSoldierAnim( pSoldier, CATCH_CROUCHED, 0 , FALSE );
2030 					}
2031 
2032 					pObject->fCatchAnimOn = TRUE;
2033 				}
2034 			}
2035 
2036 			pObject->fAttemptedCatch = TRUE;
2037 
2038 			if ( uiSpacesAway <= 1 && !pObject->fCatchCheckDone )
2039 			{
2040 				if ( AttemptToCatchObject( pObject ) )
2041 				{
2042 					return( TRUE );
2043 				}
2044 			}
2045 		}
2046 	}
2047 	return( FALSE );
2048 }
2049 
2050 
AttemptToCatchObject(REAL_OBJECT * pObject)2051 static BOOLEAN AttemptToCatchObject(REAL_OBJECT* pObject)
2052 {
2053 	UINT8 ubChanceToCatch;
2054 
2055 	// OK, get chance to catch
2056 	// base it on...? CC? Dexterity?
2057 	ubChanceToCatch = 50 + EffectiveDexterity(pObject->target) / 2;
2058 
2059 	SLOGD("Chance To Catch: %d", ubChanceToCatch );
2060 
2061 	pObject->fCatchCheckDone = TRUE;
2062 
2063 	if ( PreRandom( 100 ) > ubChanceToCatch )
2064 	{
2065 		return( FALSE );
2066 	}
2067 
2068 	pObject->fCatchGood = TRUE;
2069 
2070 	return( TRUE );
2071 }
2072 
2073 
DoCatchObject(REAL_OBJECT * pObject)2074 static BOOLEAN DoCatchObject(REAL_OBJECT* pObject)
2075 {
2076 	BOOLEAN fGoodCatch = FALSE;
2077 	UINT16  usItem;
2078 
2079 	// Get intended target
2080 	SOLDIERTYPE* const pSoldier = pObject->target;
2081 
2082 	// Catch anim.....
2083 	switch( gAnimControl[ pSoldier->usAnimState ].ubHeight )
2084 	{
2085 		case ANIM_STAND:
2086 
2087 			pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
2088 			EVENT_InitNewSoldierAnim( pSoldier, END_CATCH, 0 , FALSE );
2089 			break;
2090 
2091 		case ANIM_CROUCH:
2092 
2093 			pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
2094 			EVENT_InitNewSoldierAnim( pSoldier, END_CROUCH_CATCH, 0 , FALSE );
2095 			break;
2096 	}
2097 
2098 	PlayLocationJA2Sample(pSoldier->sGridNo, CATCH_OBJECT, MIDVOLUME, 1);
2099 
2100 	pObject->fCatchAnimOn = FALSE;
2101 
2102 	if ( !pObject->fCatchGood )
2103 	{
2104 		return( FALSE );
2105 	}
2106 
2107 	// Get item
2108 	usItem = pObject->Obj.usItem;
2109 
2110 	// Transfer object
2111 	fGoodCatch = AutoPlaceObject( pSoldier, &(pObject->Obj), TRUE );
2112 
2113 	// Report success....
2114 	if ( fGoodCatch )
2115 	{
2116 		pObject->fDropItem = FALSE;
2117 
2118 		ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, st_format_printf(pMessageStrings[ MSG_MERC_CAUGHT_ITEM ], pSoldier->name, ShortItemNames[ usItem ]) );
2119 	}
2120 
2121 	return( TRUE );
2122 }
2123 
2124 
2125 //#define TESTDUDEXPLOSIVES
2126 
2127 
HandleArmedObjectImpact(REAL_OBJECT * pObject)2128 static void HandleArmedObjectImpact(REAL_OBJECT* pObject)
2129 {
2130 	INT16      sZ;
2131 	BOOLEAN    fDoImpact = FALSE;
2132 	BOOLEAN    fCheckForDuds = FALSE;
2133 	OBJECTTYPE *pObj;
2134 	INT32      iTrapped = 0;
2135 	UINT16     usFlags = 0;
2136 	INT8       bLevel = 0;
2137 
2138 	// Calculate pixel position of z
2139 	sZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)( pObject->Position.z ) ) - gpWorldLevelData[ pObject->sGridNo ].sHeight;
2140 
2141 	// get OBJECTTYPE
2142 	pObj = &(pObject->Obj);
2143 
2144 	// ATE: Make sure number of objects is 1...
2145 	pObj->ubNumberOfObjects = 1;
2146 
2147 	if ( GCM->getItem(pObj->usItem)->isGrenade()  )
2148 	{
2149 		fCheckForDuds = TRUE;
2150 	}
2151 
2152 	if ( pObj->usItem == MORTAR_SHELL )
2153 	{
2154 		fCheckForDuds = TRUE;
2155 	}
2156 
2157 	if ( GCM->getItem(pObj->usItem)->isThrown()  )
2158 	{
2159 		AddItemToPool( pObject->sGridNo, pObj, INVISIBLE, bLevel, usFlags, 0 );
2160 	}
2161 
2162 	if ( fCheckForDuds )
2163 	{
2164 		// If we landed on anything other than the floor, always! go off...
2165 	#ifdef TESTDUDEXPLOSIVES
2166 		if ( sZ != 0 || pObject->fInWater )
2167 	#else
2168 		if ( sZ != 0 || pObject->fInWater || ( pObj->bStatus[0] >= USABLE && ( PreRandom( 100 ) < (UINT32) pObj->bStatus[0] + PreRandom( 50 ) ) ) )
2169 	#endif
2170 		{
2171 			fDoImpact = TRUE;
2172 		}
2173 		else	// didn't go off!
2174 		{
2175 	#ifdef TESTDUDEXPLOSIVES
2176 			if ( 1 )
2177 	#else
2178 			if ( pObj->bStatus[0] >= USABLE && PreRandom(100) < (UINT32) pObj->bStatus[0] + PreRandom( 50 ) )
2179 	#endif
2180 			{
2181 				iTrapped = PreRandom( 4 ) + 2;
2182 			}
2183 
2184 			if ( iTrapped )
2185 			{
2186 				// Start timed bomb...
2187 				usFlags |= WORLD_ITEM_ARMED_BOMB;
2188 
2189 				pObj->bDetonatorType = BOMB_TIMED;
2190 				pObj->bDelay = (INT8)( 1 + PreRandom( 2 ) );
2191 			}
2192 
2193 			// ATE: If we have collided with roof last...
2194 			if ( pObject->iOldCollisionCode == COLLISION_ROOF )
2195 			{
2196 				bLevel = 1;
2197 			}
2198 
2199 			// Add item to pool....
2200 			AddItemToPool( pObject->sGridNo, pObj, INVISIBLE, bLevel, usFlags, 0 );
2201 
2202 			// All teams lok for this...
2203 			NotifySoldiersToLookforItems( );
2204 
2205 			if (pObject->owner != NULL)
2206 			{
2207 				DoMercBattleSound(pObject->owner, BATTLE_SOUND_CURSE1);
2208 			}
2209 		}
2210 	}
2211 	else
2212 	{
2213 		fDoImpact = TRUE;
2214 	}
2215 
2216 	if ( fDoImpact )
2217 	{
2218 		if ( pObject->Obj.usItem == BREAK_LIGHT )
2219 		{
2220 			//if the light object will be created OFF the ground
2221 			if (pObject->Position.z > 0)
2222 			{
2223 				//we cannot create the light source above the ground, or on a roof.  The system doesnt support it.
2224 				AddItemToPool(pObject->sGridNo, &(pObject->Obj), VISIBLE, 1, 0, -1);
2225 			}
2226 			else
2227 			{
2228 				// Add a light effect...
2229 				NewLightEffect(pObject->sGridNo, LIGHT_FLARE_MARK_1);
2230 			}
2231 		}
2232 		else if ( GCM->getItem(pObject->Obj.usItem)->isGrenade()  )
2233 		{
2234 /* ARM: Removed.  Rewards even missed throws, and pulling a pin doesn't really teach anything about explosives
2235 			if (pObject->owner->bTeam == OUR_TEAM && gTacticalStatus.uiFlags & INCOMBAT)
2236 			{
2237 				// tossed grenade, not a dud, so grant xp
2238 				// EXPLOSIVES GAIN (10):  Tossing grenade
2239 				if (pObject->owner != NULL)
2240 				{
2241 					StatChange(*pObject->owner, EXPLODEAMT, 10, FALSE);
2242 				}
2243 			}
2244 */
2245 
2246 			IgniteExplosionXY(pObject->owner, pObject->Position.x, pObject->Position.y, sZ, pObject->sGridNo, pObject->Obj.usItem, GET_OBJECT_LEVEL(pObject->Position.z - CONVERT_PIXELS_TO_HEIGHTUNITS(gpWorldLevelData[pObject->sGridNo].sHeight)));
2247 		}
2248 		else if ( pObject->Obj.usItem == MORTAR_SHELL )
2249 		{
2250 			sZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
2251 
2252 			IgniteExplosionXY(pObject->owner, pObject->Position.x, pObject->Position.y, sZ, pObject->sGridNo, pObject->Obj.usItem, GET_OBJECT_LEVEL(pObject->Position.z - CONVERT_PIXELS_TO_HEIGHTUNITS(gpWorldLevelData[pObject->sGridNo].sHeight)));
2253 		}
2254 	}
2255 }
2256 
2257 
SavePhysicsTableToSaveGameFile(HWFILE const hFile)2258 void SavePhysicsTableToSaveGameFile(HWFILE const hFile)
2259 {
2260 	UINT16 usCnt=0;
2261 	UINT32 usPhysicsCount=0;
2262 
2263 	for( usCnt=0; usCnt<NUM_OBJECT_SLOTS; usCnt++ )
2264 	{
2265 		//if the REAL_OBJECT is active, save it
2266 		if( ObjectSlots[ usCnt ].fAllocated )
2267 		{
2268 			usPhysicsCount++;
2269 		}
2270 	}
2271 
2272 
2273 	//Save the number of REAL_OBJECTs in the array
2274 	FileWrite(hFile, &usPhysicsCount, sizeof(UINT32));
2275 
2276 	if( usPhysicsCount != 0 )
2277 	{
2278 		for( usCnt=0; usCnt<NUM_OBJECT_SLOTS; usCnt++ )
2279 		{
2280 			const REAL_OBJECT* const o = &ObjectSlots[usCnt];
2281 			if (o->fAllocated) InjectRealObjectIntoFile(hFile, o);
2282 		}
2283 	}
2284 }
2285 
2286 
LoadPhysicsTableFromSavedGameFile(HWFILE const hFile)2287 void LoadPhysicsTableFromSavedGameFile(HWFILE const hFile)
2288 {
2289 	UINT16 usCnt=0;
2290 
2291 	//make sure the objects are not allocated
2292 	std::fill_n(ObjectSlots, NUM_OBJECT_SLOTS, REAL_OBJECT{});
2293 
2294 	//Load the number of REAL_OBJECTs in the array
2295 	FileRead(hFile, &guiNumObjectSlots, sizeof(UINT32));
2296 
2297 	//loop through and add the objects
2298 	for( usCnt=0; usCnt<guiNumObjectSlots; usCnt++ )
2299 	{
2300 		REAL_OBJECT* const o = &ObjectSlots[usCnt];
2301 		ExtractRealObjectFromFile(hFile, o);
2302 	}
2303 }
2304 
2305 
RandomGridFromRadius(INT16 sSweetGridNo,INT8 ubMinRadius,INT8 ubMaxRadius)2306 static UINT16 RandomGridFromRadius(INT16 sSweetGridNo, INT8 ubMinRadius, INT8 ubMaxRadius)
2307 {
2308 	INT16  sX, sY;
2309 	INT16  sGridNo;
2310 	INT32  leftmost;
2311 	UINT32 cnt = 0;
2312 
2313 	if ( ubMaxRadius == 0 || ubMinRadius == 0 )
2314 	{
2315 		return( sSweetGridNo );
2316 	}
2317 
2318 	for (;;)
2319 	{
2320 		sX = (UINT16)PreRandom( ubMaxRadius );
2321 		sY = (UINT16)PreRandom( ubMaxRadius );
2322 
2323 		if ( ( sX < ubMinRadius || sY < ubMinRadius ) && ubMaxRadius != ubMinRadius )
2324 		{
2325 			continue;
2326 		}
2327 
2328 		if ( PreRandom( 2 ) == 0 )
2329 		{
2330 			sX = sX * -1;
2331 		}
2332 
2333 		if ( PreRandom( 2 ) == 0 )
2334 		{
2335 			sY = sY * -1;
2336 		}
2337 
2338 		leftmost = ( ( sSweetGridNo + ( WORLD_COLS * sY ) )/ WORLD_COLS ) * WORLD_COLS;
2339 
2340 		sGridNo = sSweetGridNo + ( WORLD_COLS * sY ) + sX;
2341 
2342 		if ( sGridNo == sSweetGridNo )
2343 		{
2344 			continue;
2345 		}
2346 
2347 		if (++cnt > 50) return NOWHERE;
2348 
2349 		if ( sGridNo >=0 && sGridNo < WORLD_MAX &&
2350 			sGridNo >= leftmost && sGridNo < ( leftmost + WORLD_COLS ) )
2351 		{
2352 			return sGridNo;
2353 		}
2354 	}
2355 }
2356