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