Files
source/TileEngine/physics.cpp
T
silversurfer 8257d11276 -Fix for throwing explosives with attachments (by Sevenfm)
Weight is now considered correctly.

-Civilians now take care not to step on player placed tripwire. (by Sevenfm)


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@6585 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2013-11-12 11:46:45 +00:00

2905 lines
81 KiB
C++

#include "builddefines.h"
#ifdef PRECOMPILEDHEADERS
#include "TileEngine All.h"
#else
#include "physics.h"
#include "wcheck.h"
#include "timer control.h"
#include "isometric utils.h"
#include "los.h"
#include "worldman.h"
#include "event pump.h"
#include "Sound Control.h"
//#include "soldier control.h"
#include "interface.h"
#include "interface items.h"
#include "weapons.h"
#include "explosion control.h"
#include "Debug Control.h"
#include "tile animation.h"
#include "message.h"
#include "weapons.h"
#include "structure wrap.h"
#include "physics.h"
#include "overhead.h"
#include "animation control.h"
#include "text.h"
#include "Random.h"
#include "lighteffects.h"
#include "opplist.h"
#include "World Items.h"
#include "environment.h"
#include "GameSettings.h"
#include "Buildings.h"
#endif
#include "Campaign.h"
#include "SkillCheck.h"
#include "connect.h"
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
class SOLDIERTYPE;
#define NO_TEST_OBJECT 0
#define TEST_OBJECT_NO_COLLISIONS 1
#define TEST_OBJECT_ANY_COLLISION 2
#define TEST_OBJECT_NOTWALLROOF_COLLISIONS 3
#define OUTDOORS_START_ANGLE (FLOAT)( PI/4 )
#define INDOORS_START_ANGLE (FLOAT)( PI/30 )
//#define INDOORS_START_ANGLE (FLOAT)( 0 )
#define GLAUNCHER_START_ANGLE (FLOAT)( PI/8 )
#define GLAUNCHER_HIGHER_LEVEL_START_ANGLE (FLOAT)( PI/6 )
#define GET_THROW_HEIGHT( l ) (INT16)( ( l * 256 ) )
//#define GET_SOLDIER_THROW_HEIGHT( l ) (INT16)( ( l * 256 ) + STANDING_HEIGHT )
// ATE: Commented out part to provide a more accurate start height
#define GET_SOLDIER_THROW_HEIGHT( l ) (INT16)( ( l * 256 ) + CROUCHED_HEIGHT )
#define GET_OBJECT_LEVEL( z ) ( (INT8)( ( z + 10 ) / HEIGHT_UNITS ) )
//#define OBJECT_DETONATE_ON_IMPACT( object ) ( ( object->Obj.usItem == MORTAR_SHELL ) ) // && ( object->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) )
// HEADROCK HAM 5: Enabled "Explode on Impact" flag for explosive items
#define OBJECT_DETONATE_ON_IMPACT( object ) ( ( Item[object->Obj.usItem].usItemClass == IC_BOMB ) || ( Explosive[Item[object->Obj.usItem].ubClassIndex ].fExplodeOnImpact ) ) // && ( object->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) )
#define MAX_INTEGRATIONS 8
#define TIME_MULTI 1.8
//#define TIME_MULTI 2.2
#define DELTA_T ( 1.0 * TIME_MULTI )
#define GRAVITY ( 9.8 * 2.5 )
//#define GRAVITY ( 9.8 * 2.8 )
REAL_OBJECT ObjectSlots[ NUM_OBJECT_SLOTS ];
UINT32 guiNumObjectSlots = 0;
BOOLEAN fDampingActive = FALSE;
//real Kdl = (float)0.5; // LINEAR DAMPENING ( WIND RESISTANCE )
real Kdl = (float)( 0.1 * TIME_MULTI ); // LINEAR DAMPENING ( WIND RESISTANCE )
#define EPSILONV 0.5
#define EPSILONP (real)0.01
#define EPSILONPZ 3
#define CALCULATE_OBJECT_MASS( m ) ( (float)( m * 2 ) )
#define SCALE_VERT_VAL_TO_HORZ( f ) ( ( f / HEIGHT_UNITS ) * CELL_X_SIZE )
#define SCALE_HORZ_VAL_TO_VERT( f ) ( ( f / CELL_X_SIZE ) * HEIGHT_UNITS )
void SimulateObject( REAL_OBJECT *pObject, real deltaT );
void CheckForObjectHittingMerc( REAL_OBJECT *pObject, UINT16 usStructureID );
extern void DoGenericHit( SOLDIERTYPE *pSoldier, UINT8 ubSpecial, INT16 bDirection );
BOOLEAN PhysicsUpdateLife( REAL_OBJECT *pObject, real DeltaTime );
BOOLEAN PhysicsComputeForces( REAL_OBJECT *pObject );
BOOLEAN PhysicsIntegrate( REAL_OBJECT *pObject, real DeltaTime );
BOOLEAN PhysicsMoveObject( REAL_OBJECT *pObject );
BOOLEAN PhysicsCheckForCollisions( REAL_OBJECT *pObject, INT32 *piCollisionID );
void PhysicsResolveCollision( REAL_OBJECT *pObject, vector_3 *pVelocity, vector_3 *pNormal, real CoefficientOfRestitution );
void PhysicsDeleteObject( REAL_OBJECT *pObject );
BOOLEAN PhysicsHandleCollisions( REAL_OBJECT *pObject, INT32 *piCollisionID, real DeltaTime );
FLOAT CalculateForceFromRange( UINT16 usItem, INT16 sRange, FLOAT dDegrees );
INT32 RandomGridFromRadius( INT32 sSweetGridNo, INT8 ubMinRadius, INT8 ubMaxRadius );
// Parameters for item throwing
#define MAX_MISS_BY 30
#define MIN_MISS_BY 1
#define MAX_MISS_RADIUS 5
// Lesh: needed to fix item throwing through window
extern INT16 DirIncrementer[8];
void HandleArmedObjectImpact( REAL_OBJECT *pObject );
void ObjectHitWindow( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce );
FLOAT CalculateObjectTrajectory( INT16 sTargetZ, OBJECTTYPE *pItem, vector_3 *vPosition, vector_3 *vForce, INT32 *psFinalGridNo );
vector_3 FindBestForceForTrajectory( INT32 sSrcGridNo, INT32 sGridNo,INT16 sStartZ, INT16 sEndZ, real dzDegrees, OBJECTTYPE *pItem, INT32 *psGridNo, FLOAT *pzMagForce );
INT32 ChanceToGetThroughObjectTrajectory( INT16 sTargetZ, OBJECTTYPE *pItem, vector_3 *vPosition, vector_3 *vForce, INT32 *psFinalGridNo, INT8 *pbLevel, BOOLEAN fFromUI );
FLOAT CalculateSoldierMaxForce( SOLDIERTYPE *pSoldier, FLOAT dDegrees, OBJECTTYPE *pObject, BOOLEAN fArmed );
BOOLEAN AttemptToCatchObject( REAL_OBJECT *pObject );
BOOLEAN CheckForCatchObject( REAL_OBJECT *pObject );
BOOLEAN DoCatchObject( REAL_OBJECT *pObject );
BOOLEAN CheckForCatcher( REAL_OBJECT *pObject, UINT16 usStructureID );
void REAL_OBJECT::initialize()
{
memset(this, 0, SIZEOF_REAL_OBJECT_POD);
this->Obj.initialize();
}
REAL_OBJECT& REAL_OBJECT::operator =(OLD_REAL_OBJECT_101 &src)
{
this->Obj = src.oldObj;
this->fAllocated = src.fAllocated;
this->fAlive = src.fAlive;
this->fApplyFriction = src.fApplyFriction;
this->fColliding = src.fColliding;
this->fZOnRest = src.fZOnRest;
this->fVisible = src.fVisible;
this->fInWater = src.fInWater;
this->fTestObject = src.fTestObject;
this->fTestEndedWithCollision = src.fTestEndedWithCollision;
this->fTestPositionNotSet = src.fTestPositionNotSet;
this->TestZTarget = src.TestZTarget;
this->OneOverMass = src.OneOverMass;
this->AppliedMu = src.AppliedMu;
this->Position = src.Position;
this->TestTargetPosition = src.TestTargetPosition;
this->OldPosition = src.OldPosition;
this->Velocity = src.Velocity;
this->OldVelocity = src.OldVelocity;
this->InitialForce = src.InitialForce;
this->Force = src.Force;
this->CollisionNormal = src.CollisionNormal;
this->CollisionVelocity = src.CollisionVelocity;
this->CollisionElasticity = src.CollisionElasticity;
this->sGridNo = src.sGridNo;
this->iID = src.iID;
this->pNode = src.pNode;
this->pShadow = src.pShadow;
this->sConsecutiveCollisions = src.sConsecutiveCollisions;
this->sConsecutiveZeroVelocityCollisions = src.sConsecutiveZeroVelocityCollisions;
this->iOldCollisionCode = src.iOldCollisionCode;
this->dLifeLength = src.dLifeLength;
this->dLifeSpan = src.dLifeSpan;
this->fFirstTimeMoved = src.fFirstTimeMoved;
this->sFirstGridNo = src.sFirstGridNo;
this->ubOwner = src.ubOwner;
this->ubActionCode = src.ubActionCode;
this->uiActionData = src.uiActionData;
this->fDropItem = src.fDropItem;
this->uiNumTilesMoved = src.uiNumTilesMoved;
this->fCatchGood = src.fCatchGood;
this->fAttemptedCatch = src.fAttemptedCatch;
this->fCatchAnimOn = src.fCatchAnimOn;
this->fCatchCheckDone = src.fCatchCheckDone;
this->fEndedWithCollisionPositionSet = src.fEndedWithCollisionPositionSet;
this->EndedWithCollisionPosition = src.EndedWithCollisionPosition;
this->fHaveHitGround = src.fHaveHitGround;
this->fPotentialForDebug = src.fPotentialForDebug;
this->sLevelNodeGridNo = src.sLevelNodeGridNo;
this->iSoundID = src.iSoundID;
this->ubLastTargetTakenDamage = src.ubLastTargetTakenDamage;
return *this;
}
/// OBJECT POOL FUNCTIONS
INT32 GetFreeObjectSlot(void)
{
UINT32 uiCount;
for(uiCount=0; uiCount < guiNumObjectSlots; uiCount++)
{
if(( ObjectSlots[uiCount].fAllocated == FALSE ) )
return((INT32)uiCount);
}
if(guiNumObjectSlots < NUM_OBJECT_SLOTS )
return((INT32)guiNumObjectSlots++);
return(-1);
}
void RecountObjectSlots(void)
{
INT32 uiCount;
for(uiCount=guiNumObjectSlots-1; (uiCount >=0) ; uiCount--)
{
if( ( ObjectSlots[uiCount].fAllocated ) )
{
guiNumObjectSlots=(UINT32)(uiCount+1);
return;
}
}
guiNumObjectSlots = 0;
}
INT32 CreatePhysicalObject( OBJECTTYPE *pGameObj, real dLifeLength, real xPos, real yPos, real zPos, real xForce, real yForce, real zForce, UINT8 ubOwner, UINT8 ubActionCode, UINT32 uiActionData, BOOLEAN fTestObject )
{
INT32 iObjectIndex;
FLOAT mass;
REAL_OBJECT *pObject;
if( ( iObjectIndex = GetFreeObjectSlot() )==(-1) )
return(-1);
pObject = &( ObjectSlots[ iObjectIndex ] );
pObject->initialize();
// OK, GET OBJECT DATA AND COPY
pObject->Obj = *pGameObj;
// Get mass
mass = CALCULATE_OBJECT_MASS( Item[pGameObj->usItem ].ubWeight );
// If mass is z, make it something!
if ( mass == 0 )
{
mass = 10;
}
// OK, mass determines the smoothness of the physics integration
// For gameplay, we will use mass for maybe max throw distance
mass = 60;
// Set lifelength
pObject->dLifeLength = dLifeLength;
pObject->fAllocated = TRUE;
pObject->fAlive = TRUE;
pObject->fApplyFriction = FALSE;
pObject->iSoundID = NO_SAMPLE;
// Set values
pObject->OneOverMass = 1 / mass;
pObject->Position.x = xPos;
pObject->Position.y = yPos;
pObject->Position.z = zPos;
pObject->fVisible = TRUE;
pObject->fTestObject = fTestObject;
pObject->ubOwner = ubOwner;
pObject->ubActionCode = ubActionCode;
pObject->uiActionData = uiActionData;
pObject->fDropItem = TRUE;
pObject->ubLastTargetTakenDamage = NOBODY;
pObject->fFirstTimeMoved = TRUE;
pObject->InitialForce.x = SCALE_VERT_VAL_TO_HORZ( xForce );
pObject->InitialForce.y = SCALE_VERT_VAL_TO_HORZ( yForce );
pObject->InitialForce.z = zForce ;
pObject->InitialForce = VDivScalar( &(pObject->InitialForce), (float)TIME_MULTI );
pObject->InitialForce = VMultScalar( &(pObject->InitialForce), 1.5 );
// Calculate gridNo
pObject->sGridNo = MAPROWCOLTOPOS( ( (INT16)yPos / CELL_Y_SIZE ), ( (INT16)xPos / CELL_X_SIZE ) );
pObject->iID = iObjectIndex;
pObject->pNode = NULL;
pObject->pShadow = NULL;
// If gridno not equal to NOWHERE, use sHeight of alnd....
if (!TileIsOutOfBounds(pObject->sGridNo))
{
pObject->Position.z += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pObject->sGridNo ].sHeight );
pObject->EndedWithCollisionPosition.z += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pObject->sGridNo ].sHeight );
}
PhysicsDebugMsg( String( "NewPhysics Object") );
// If this is a real object being created, then increase the ABC
if (!fTestObject)
{
gTacticalStatus.ubAttackBusyCount++;
DebugAttackBusy( String( "@@@@@@@ Increasing attacker busy count..., PHYSICS OBJECT effect started. Now %d\n", gTacticalStatus.ubAttackBusyCount) );
}
return( iObjectIndex );
}
BOOLEAN RemoveObjectSlot( INT32 iObject )
{
CHECKF( iObject < NUM_OBJECT_SLOTS );
ObjectSlots[iObject].fAllocated=FALSE;
RecountObjectSlots();
return( TRUE );
}
void SimulateWorld( )
{
UINT32 cnt;
REAL_OBJECT *pObject;
if ( COUNTERDONE( PHYSICSUPDATE ) )
{
RESETCOUNTER( PHYSICSUPDATE );
for( cnt = 0;cnt < guiNumObjectSlots; cnt++)
{
// CHECK FOR ALLOCATED
if( ObjectSlots[ cnt ].fAllocated )
{
// Get object
pObject = &( ObjectSlots[ cnt ] );
SimulateObject( pObject, (real)DELTA_T );
}
}
}
}
void RemoveAllPhysicsObjects( )
{
UINT32 cnt;
for( cnt = 0;cnt < guiNumObjectSlots; cnt++)
{
// CHECK FOR ALLOCATED
if( ObjectSlots[ cnt ].fAllocated )
{
PhysicsDeleteObject( &(ObjectSlots[ cnt ]) );
}
}
}
void SimulateObject( REAL_OBJECT *pObject, real deltaT )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject");
if ( !PhysicsUpdateLife( pObject, (float)deltaT ) )
{
return;
}
// DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: check life");
if ( pObject->fAlive )
{
//DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: do subtime");
// Do subtime here....
if ( !PhysicsComputeForces( pObject ) )
{
// DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: couldn't compute forces");
return;
}
real CurrentTime = 0;
real DeltaTime = (float)deltaT / 10.0f;
real TargetTime = (float)deltaT;
INT32 iCollisionID;
BOOLEAN fEndThisObject = FALSE;
while( CurrentTime < TargetTime )
{
//MADD TO KAIDEN: HERE IS THE PROBLEM: For some reason fEndThisObject is never set to true
// DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("SimulateObject: in loop while CurrentTime < TargetTime: %d < %d",CurrentTime,TargetTime));
if ( !PhysicsIntegrate( pObject, DeltaTime ) )
{
fEndThisObject = TRUE;
break;
}
if ( !PhysicsHandleCollisions( pObject, &iCollisionID, DeltaTime ) )
{
fEndThisObject = TRUE;
break;
}
if ( iCollisionID != COLLISION_NONE )
{
break;
}
CurrentTime += DeltaTime;
}
//DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: check if ending this object");
if ( fEndThisObject )
{
//DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: object ended, returning");
return;
}
//DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: try moving the object");
if ( !PhysicsMoveObject( pObject ) )
{
//DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: couldn't move object, returning");
return;
}
}
}
BOOLEAN PhysicsComputeForces( REAL_OBJECT *pObject )
{
vector_3 vTemp;
// Calculate forces
pObject->Force = VSetEqual( &(pObject->InitialForce ) );
// Note: Only apply gravity if we are not resting on some structure surface
if ( !pObject->fZOnRest )
{
pObject->Force.z -= (real)GRAVITY;
}
// Set intial force to zero
pObject->InitialForce = VMultScalar( &(pObject->InitialForce ), 0 );
if ( pObject->fApplyFriction )
{
vTemp = VMultScalar( &(pObject->Velocity), -pObject->AppliedMu );
pObject->Force = VAdd( &(vTemp), &(pObject->Force) );
pObject->fApplyFriction = FALSE;
}
if( fDampingActive )
{
vTemp = VMultScalar( &(pObject->Velocity), -Kdl );
pObject->Force = VAdd( &(vTemp), &(pObject->Force) );
}
return( TRUE );
}
BOOLEAN PhysicsUpdateLife( REAL_OBJECT *pObject, real DeltaTime )
{
UINT8 bLevel = 0;
pObject->dLifeSpan += DeltaTime;
// End life if time has ran out or we are stationary
if ( pObject->dLifeLength != -1 )
{
if ( pObject->dLifeSpan > pObject->dLifeLength )
{
pObject->fAlive = FALSE;
}
}
// WANNE.BMP: Only do this check on normal maps.
// When we do this check on Big Maps it always returns false and we got wrong physics when trowing items!
if (WORLD_COLS <= 160 && WORLD_ROWS <= 160)
{
// End life if we are out of bounds....
if ( !GridNoOnVisibleWorldTile( pObject->sGridNo ) )
{
pObject->fAlive = FALSE;
}
}
if ( !pObject->fAlive )
{
// ATE: OK, adjust gridno based on where we ended and if we hit any walls....
{
// Check for SW wall...
real remainder;
remainder = (float)fmod( pObject->Position.x, CELL_X_SIZE );
if ( remainder >= 9 )
{
// DO we have a wall here?
if ( WallOrClosedDoorExistsOfTopRightOrientation( pObject->sGridNo ) )
{
// Adjust forward 1!
pObject->sGridNo += 1;
}
}
// Now SE wall
remainder = (float)fmod( pObject->Position.y, CELL_X_SIZE );
if ( remainder >= 9 )
{
// DO we have a wall here?
if ( WallOrClosedDoorExistsOfTopLeftOrientation( pObject->sGridNo ) )
{
// Adjust forward 1!
pObject->sGridNo += WORLD_COLS;
}
}
}
pObject->fAlive = FALSE;
if ( !pObject->fTestObject )
{
if ( pObject->iSoundID != NO_SAMPLE )
{
SoundStop( pObject->iSoundID );
}
if ( pObject->ubActionCode == THROW_ARM_ITEM && !pObject->fInWater )
{
HandleArmedObjectImpact( pObject );
}
else
{
// If we are in water, and we are a sinkable item...
// if ( !pObject->fInWater || !( Item[ pObject->Obj.usItem ].fFlags & ITEM_SINKS ) )
if ( !pObject->fInWater || !( Item[ pObject->Obj.usItem ].sinks ) )
{
if ( pObject->fDropItem )
{
// ATE: If we have collided with roof last...
if ( pObject->iOldCollisionCode == COLLISION_ROOF )
{
bLevel = 1;
}
// ATE: Yet another hack here, to make sure things don't fall through roofs
if ( pObject->ubLastTargetTakenDamage != NOBODY )
{
SOLDIERTYPE *pSoldier;
pSoldier = MercPtrs[ pObject->ubLastTargetTakenDamage ];
bLevel = pSoldier->pathing.bLevel;
}
// ATE; If an armed object, don't add....
if ( pObject->ubActionCode != THROW_ARM_ITEM )
{
AddItemToPool( pObject->sGridNo, &( pObject->Obj ), 1, bLevel, 0, -1 );
}
}
}
}
// Make impact noise....
if ( Item[pObject->Obj.usItem].rock )
{
MakeNoise( pObject->ubOwner, pObject->sGridNo, 0, gpWorldLevelData[ pObject->sGridNo ].ubTerrainID, (UINT8) (9 + PreRandom( 9 ) ), NOISE_ROCK_IMPACT );
}
else if ( Item[ pObject->Obj.usItem ].usItemClass & IC_GRENADE )
{
MakeNoise( pObject->ubOwner, pObject->sGridNo, 0, gpWorldLevelData[ pObject->sGridNo ].ubTerrainID, (UINT8) (9 + PreRandom( 9 ) ), NOISE_GRENADE_IMPACT );
}
if ( !pObject->fTestObject && pObject->iOldCollisionCode == COLLISION_GROUND )
{
PlayJA2Sample( THROW_IMPACT_2, RATE_11025, SoundVolume( MIDVOLUME, pObject->sGridNo ), 1, SoundDir( pObject->sGridNo ) );
}
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Reducing attacker busy count..., PHYSICS OBJECT DONE effect gone off") );
DebugAttackBusy( "@@@@@@@ PHYSICS OBJECT effect finished\n" );
ReduceAttackBusyCount( );
// ATE: Handle end of animation...
if ( pObject->fCatchAnimOn )
{
SOLDIERTYPE *pSoldier;
pObject->fCatchAnimOn = FALSE;
// Get intended target
pSoldier = MercPtrs[ pObject->uiActionData ];
// Catch anim.....
switch( gAnimControl[ pSoldier->usAnimState ].ubHeight )
{
case ANIM_STAND:
pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
pSoldier->EVENT_InitNewSoldierAnim( END_CATCH, 0 , FALSE );
break;
case ANIM_CROUCH:
pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
pSoldier->EVENT_InitNewSoldierAnim( END_CROUCH_CATCH, 0 , FALSE );
break;
}
PlayJA2Sample( CATCH_OBJECT, RATE_11025, SoundVolume( MIDVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
}
}
PhysicsDeleteObject( pObject );
return( FALSE );
}
return( TRUE );
}
BOOLEAN PhysicsIntegrate( REAL_OBJECT *pObject, real DeltaTime )
{
vector_3 vTemp;
// Save old position
pObject->OldPosition = VSetEqual( &(pObject->Position) );
pObject->OldVelocity = VSetEqual( &(pObject->Velocity) );
vTemp = VMultScalar( &(pObject->Velocity), DeltaTime );
pObject->Position = VAdd( &(pObject->Position), &vTemp );
// Save test TargetPosition
if ( pObject->fTestPositionNotSet )
{
pObject->TestTargetPosition = VSetEqual( &(pObject->Position) );
}
vTemp = VMultScalar( &(pObject->Force), ( DeltaTime * pObject->OneOverMass ) );
pObject->Velocity = VAdd( &(pObject->Velocity), &vTemp );
if ( pObject->fPotentialForDebug )
{
PhysicsDebugMsg( String( "Object %d: Force %f %f %f", pObject->iID, pObject->Force.x, pObject->Force.y, pObject->Force.z ) );
PhysicsDebugMsg( String( "Object %d: Velocity %f %f %f", pObject->iID, pObject->Velocity.x, pObject->Velocity.y, pObject->Velocity.z ) );
PhysicsDebugMsg( String( "Object %d: Position %f %f %f", pObject->iID, pObject->Position.x, pObject->Position.y, pObject->Position.z ) );
PhysicsDebugMsg( String( "Object %d: Delta Pos %f %f %f", pObject->iID, (pObject->OldPosition.x - pObject->Position.x ), (pObject->OldPosition.y - pObject->Position.y ), ( pObject->OldPosition.z - pObject->Position.z ) ) );
}
// if ( pObject->Obj.usItem == MORTAR_SHELL && !pObject->fTestObject && pObject->ubActionCode == THROW_ARM_ITEM )
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 524, usItemClass=%d",Item[pObject->Obj.usItem].usItemClass));
if ( Item[pObject->Obj.usItem].usItemClass == IC_BOMB && !pObject->fTestObject && pObject->ubActionCode == THROW_ARM_ITEM )
{
// Start soud if we have reached our max height
if ( pObject->OldVelocity.z >= 0 && pObject->Velocity.z < 0 )
{
if ( pObject->iSoundID == NO_SAMPLE )
{
pObject->iSoundID = PlayJA2Sample( MORTAR_WHISTLE, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN );
}
}
}
return( TRUE );
}
BOOLEAN PhysicsHandleCollisions( REAL_OBJECT *pObject, INT32 *piCollisionID, real DeltaTime )
{
FLOAT dDeltaX, dDeltaY, dDeltaZ;
if ( PhysicsCheckForCollisions( pObject, piCollisionID ) )
{
dDeltaX = pObject->Position.x - pObject->OldPosition.x;
dDeltaY = pObject->Position.y - pObject->OldPosition.y;
dDeltaZ = pObject->Position.z - pObject->OldPosition.z;
if ( dDeltaX <= EPSILONV && dDeltaX >= -EPSILONV &&
dDeltaY <= EPSILONV && dDeltaY >= -EPSILONV )
{
pObject->sConsecutiveZeroVelocityCollisions++;
}
if ( pObject->sConsecutiveZeroVelocityCollisions > 3 )
{
// We will continue with our Z velocity
pObject->Velocity.x = 0;
pObject->Velocity.y = 0;
// Check that we are not colliding with structure z
//if ( *piCollisionID == COLLISION_STRUCTURE_Z || *piCollisionID == COLLISION_ROOF )
if ( *piCollisionID == COLLISION_STRUCTURE_Z || *piCollisionID == COLLISION_ROOF || *piCollisionID == COLLISION_GROUND )
{
pObject->Velocity.z = 0;
// Set us not alive!
pObject->fAlive = FALSE;
}
*piCollisionID = COLLISION_NONE;
}
else
{
// Set position back to before collision
pObject->Position = VSetEqual( &(pObject->OldPosition) );
// Set old position!
pObject->OldPosition.x = pObject->Position.y - dDeltaX;
pObject->OldPosition.y = pObject->Position.x - dDeltaY;
pObject->OldPosition.z = pObject->Position.z - dDeltaZ;
PhysicsResolveCollision( pObject, &(pObject->CollisionVelocity), &(pObject->CollisionNormal), pObject->CollisionElasticity );
}
if ( pObject->Position.z < 0 )
{
pObject->Position.z = 0;
}
//otherwise, continue falling downwards!
// TO STOP?
// Check for delta position values
if ( dDeltaZ <= EPSILONP && dDeltaZ >= -EPSILONP &&
dDeltaY <= EPSILONP && dDeltaY >= -EPSILONP &&
dDeltaX <= EPSILONP && dDeltaX >= -EPSILONP )
{
//pObject->fAlive = FALSE;
//return( FALSE );
}
// Check for repeated collisions...
//if ( pObject->iOldCollisionCode == COLLISION_ROOF || pObject->iOldCollisionCode == COLLISION_GROUND || pObject->iOldCollisionCode == COLLISION_WATER )
{
// ATE: This is a safeguard
if ( pObject->sConsecutiveCollisions > 30 )
{
pObject->fAlive = FALSE;
return( FALSE );
}
}
// Check for -ve velocity still...
/*if ( pObject->Velocity.z <= EPSILONV && pObject->Velocity.z >= -EPSILONV &&
pObject->Velocity.y <= EPSILONV && pObject->Velocity.y >= -EPSILONV &&
pObject->Velocity.x <= EPSILONV && pObject->Velocity.x >= -EPSILONV )
{
PhysicsDeleteObject( pObject );
pObject->fAlive = FALSE;
return( FALSE );
}*/
}
return( TRUE );
}
void PhysicsDeleteObject( REAL_OBJECT *pObject )
{
if ( pObject->fAllocated )
{
if ( pObject->pNode != NULL )
{
RemoveStructFromLevelNode( pObject->sLevelNodeGridNo, pObject->pNode );
}
if ( pObject->pShadow != NULL )
{
RemoveShadowFromLevelNode( pObject->sLevelNodeGridNo, pObject->pShadow );
}
RemoveObjectSlot( pObject->iID );
}
}
BOOLEAN PhysicsCheckForCollisions( REAL_OBJECT *pObject, INT32 *piCollisionID )
{
vector_3 vTemp;
FLOAT dDeltaX, dDeltaY, dDeltaZ, dX, dY, dZ;
INT32 iCollisionCode = COLLISION_NONE;
BOOLEAN fDoCollision = FALSE;
FLOAT dElasity = 1;
UINT16 usStructureID = -1;
FLOAT dNormalX = 0.0, dNormalY = 0.0, dNormalZ = 1.0;
INT32 sGridNo = NOWHERE;
// Checkf for collisions
dX = pObject->Position.x;
dY = pObject->Position.y;
dZ = pObject->Position.z;
vTemp.x = 0;
vTemp.y = 0;
vTemp.z = 0;
dDeltaX = dX - pObject->OldPosition.x;
dDeltaY = dY - pObject->OldPosition.y;
dDeltaZ = dZ - pObject->OldPosition.z;
//Round delta pos to nearest 0.01
//dDeltaX = (float)( (int)dDeltaX * 100 ) / 100;
//dDeltaY = (float)( (int)dDeltaY * 100 ) / 100;
//dDeltaZ = (float)( (int)dDeltaZ * 100 ) / 100;
// SKIP FIRST GRIDNO, WE'LL COLLIDE WITH OURSELVES....
if ( pObject->fTestObject != TEST_OBJECT_NO_COLLISIONS )
{
iCollisionCode = CheckForCollision( dX, dY, dZ, dDeltaX, dDeltaY, dDeltaZ, (INT16 *)&usStructureID, &dNormalX, &dNormalY, &dNormalZ );
}
else if ( pObject->fTestObject == TEST_OBJECT_NO_COLLISIONS )
{
iCollisionCode = COLLISION_NONE;
// Are we on a downward slope?
if ( dZ < pObject->TestZTarget && dDeltaZ < 0 )
{
if (pObject->fTestPositionNotSet )
{
if ( pObject->TestZTarget > 32 )
{
pObject->fTestPositionNotSet = FALSE;
pObject->TestZTarget = 0;
}
else
{
iCollisionCode = COLLISION_GROUND;
}
}
else
{
iCollisionCode = COLLISION_GROUND;
}
}
}
// If a test object and we have collided with something ( should only be ground ( or roof? ) )
// Or destination?
if ( pObject->fTestObject == TEST_OBJECT_ANY_COLLISION )
{
if ( iCollisionCode != COLLISION_GROUND && iCollisionCode != COLLISION_ROOF && iCollisionCode != COLLISION_WATER && iCollisionCode != COLLISION_NONE )
{
pObject->fTestEndedWithCollision = TRUE;
pObject->fAlive = FALSE;
return( FALSE );
}
}
if ( pObject->fTestObject == TEST_OBJECT_NOTWALLROOF_COLLISIONS )
{
// So we don't collide with ourselves.....
if ( iCollisionCode != COLLISION_WATER && iCollisionCode != COLLISION_GROUND && iCollisionCode != COLLISION_NONE &&
iCollisionCode != COLLISION_ROOF && iCollisionCode != COLLISION_INTERIOR_ROOF &&
iCollisionCode != COLLISION_WALL_SOUTHEAST && iCollisionCode != COLLISION_WALL_SOUTHWEST &&
iCollisionCode != COLLISION_WALL_NORTHEAST && iCollisionCode != COLLISION_WALL_NORTHWEST )
{
if ( pObject->fFirstTimeMoved || pObject->sFirstGridNo == pObject->sGridNo )
{
iCollisionCode = COLLISION_NONE;
}
// If we are NOT a wall or window, ignore....
if ( pObject->uiNumTilesMoved < 4 )
{
switch( iCollisionCode )
{
case COLLISION_MERC:
case COLLISION_STRUCTURE:
case COLLISION_STRUCTURE_Z:
// Set to no collision ( we shot past )
iCollisionCode = COLLISION_NONE;
break;
}
}
}
switch( iCollisionCode )
{
// End test with any collision NOT a wall, roof...
case COLLISION_STRUCTURE:
case COLLISION_STRUCTURE_Z:
// OK, if it's mercs... don't stop
if ( usStructureID >= INVALID_STRUCTURE_ID )
{
pObject->fTestEndedWithCollision = TRUE;
if ( !pObject->fEndedWithCollisionPositionSet )
{
pObject->fEndedWithCollisionPositionSet = TRUE;
pObject->EndedWithCollisionPosition = VSetEqual( &(pObject->Position) );
}
iCollisionCode = COLLISION_NONE;
}
else
{
if ( !pObject->fEndedWithCollisionPositionSet )
{
pObject->fEndedWithCollisionPositionSet = TRUE;
pObject->EndedWithCollisionPosition = VSetEqual( &(pObject->Position) );
}
}
break;
case COLLISION_ROOF:
if ( !pObject->fEndedWithCollisionPositionSet )
{
pObject->fEndedWithCollisionPositionSet = TRUE;
pObject->EndedWithCollisionPosition = VSetEqual( &(pObject->Position) );
}
break;
case COLLISION_WATER:
case COLLISION_GROUND:
case COLLISION_MERC:
case COLLISION_INTERIOR_ROOF:
case COLLISION_NONE:
case COLLISION_WINDOW_SOUTHEAST:
case COLLISION_WINDOW_SOUTHWEST:
case COLLISION_WINDOW_NORTHEAST:
case COLLISION_WINDOW_NORTHWEST:
// Here we just keep going..
break;
default:
// THis is for walls, windows, etc
// here, we set test ended with collision, but keep going...
pObject->fTestEndedWithCollision = TRUE;
break;
}
}
if ( pObject->fTestObject != TEST_OBJECT_NOTWALLROOF_COLLISIONS )
{
if ( iCollisionCode != COLLISION_WATER && iCollisionCode != COLLISION_GROUND && iCollisionCode != COLLISION_NONE &&
iCollisionCode != COLLISION_ROOF && iCollisionCode != COLLISION_INTERIOR_ROOF &&
iCollisionCode != COLLISION_WALL_SOUTHEAST && iCollisionCode != COLLISION_WALL_SOUTHWEST &&
iCollisionCode != COLLISION_WALL_NORTHEAST && iCollisionCode != COLLISION_WALL_NORTHWEST )
{
// So we don't collide with ourselves.....
if ( pObject->fFirstTimeMoved || pObject->sFirstGridNo == pObject->sGridNo )
{
iCollisionCode = COLLISION_NONE;
}
// If we are NOT a wall or window, ignore....
if ( pObject->uiNumTilesMoved < 4 )
{
switch( iCollisionCode )
{
case COLLISION_MERC:
case COLLISION_STRUCTURE:
case COLLISION_STRUCTURE_Z:
// Set to no collision ( we shot past )
iCollisionCode = COLLISION_NONE;
break;
}
}
}
}
*piCollisionID = iCollisionCode;
// If We hit the ground
if ( iCollisionCode > COLLISION_NONE )
{
if ( pObject->iOldCollisionCode == iCollisionCode )
{
pObject->sConsecutiveCollisions++;
}
else
{
pObject->sConsecutiveCollisions = 1;
}
if ( iCollisionCode == COLLISION_WINDOW_NORTHWEST || iCollisionCode == COLLISION_WINDOW_NORTHEAST || iCollisionCode == COLLISION_WINDOW_SOUTHWEST || iCollisionCode == COLLISION_WINDOW_SOUTHEAST )
{
if ( !pObject->fTestObject )
{
// Break window!
PhysicsDebugMsg( String( "Object %d: Collision Window", pObject->iID ) );
sGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
ObjectHitWindow( sGridNo, usStructureID, FALSE, TRUE );
}
*piCollisionID = COLLISION_NONE;
return( FALSE );
}
// ATE: IF detonate on impact, stop now!
if ( OBJECT_DETONATE_ON_IMPACT( pObject ) )
{
pObject->fAlive = FALSE;
return( TRUE );
}
if ( iCollisionCode == COLLISION_GROUND )
{
vTemp.x = 0;
vTemp.y = 0;
vTemp.z = -1;
pObject->fApplyFriction = TRUE;
//pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
pObject->AppliedMu = (float)(0.34 * TIME_MULTI );
//dElasity = (float)1.5;
dElasity = (float)1.3;
fDoCollision = TRUE;
if ( !pObject->fTestObject && !pObject->fHaveHitGround )
{
PlayJA2Sample( THROW_IMPACT_2, RATE_11025, SoundVolume( MIDVOLUME, pObject->sGridNo ), 1, SoundDir( pObject->sGridNo ) );
}
pObject->fHaveHitGround = TRUE;
}
else if ( iCollisionCode == COLLISION_WATER )
{
ANITILE_PARAMS AniParams;
ANITILE *pNode;
// Continue going...
pObject->fApplyFriction = TRUE;
pObject->AppliedMu = (float)(1.54 * TIME_MULTI );
sGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
// Make thing unalive...
pObject->fAlive = FALSE;
// If first time...
if ( pObject->fVisible )
{
if ( pObject->fTestObject == NO_TEST_OBJECT )
{
// Make invisible
pObject->fVisible = FALSE;
// JA25 CJC Oct 13 1999 - if node pointer is null don't try to set flags inside it!
if( pObject->pNode )
{
pObject->pNode->uiFlags |= LEVELNODE_HIDDEN;
}
pObject->fInWater = TRUE;
// Make ripple
memset( &AniParams, 0, sizeof( ANITILE_PARAMS ) );
AniParams.sGridNo = sGridNo;
AniParams.ubLevelID = ANI_STRUCT_LEVEL;
AniParams.usTileType = THIRDMISS;
AniParams.usTileIndex = THIRDMISS1;
AniParams.sDelay = 50;
AniParams.sStartFrame = 0;
AniParams.uiFlags = ANITILE_FORWARD;
if ( pObject->ubActionCode == THROW_ARM_ITEM )
{
gTacticalStatus.ubAttackBusyCount++;
DebugAttackBusy( String( "Incrementing attack busy because of delayed water explosion. Now %d\n", gTacticalStatus.ubAttackBusyCount ) );
AniParams.ubKeyFrame1 = 11;
AniParams.uiKeyFrame1Code = ANI_KEYFRAME_CHAIN_WATER_EXPLOSION;
AniParams.uiUserData = pObject->Obj.usItem;
AniParams.ubUserData2 = pObject->ubOwner;
}
pNode = CreateAnimationTile( &AniParams );
// Adjust for absolute positioning
pNode->pLevelNode->uiFlags |= LEVELNODE_USEABSOLUTEPOS;
pNode->pLevelNode->sRelativeX = (INT16)pObject->Position.x;
pNode->pLevelNode->sRelativeY = (INT16)pObject->Position.y;
pNode->pLevelNode->sRelativeZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
}
}
}
else if ( iCollisionCode == COLLISION_ROOF || iCollisionCode == COLLISION_INTERIOR_ROOF )
{
vTemp.x = 0;
vTemp.y = 0;
vTemp.z = -1;
pObject->fApplyFriction = TRUE;
pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
dElasity = (float)1.4;
fDoCollision = TRUE;
}
//else if ( iCollisionCode == COLLISION_INTERIOR_ROOF )
//{
// vTemp.x = 0;
// vTemp.y = 0;
// vTemp.z = 1;
// pObject->fApplyFriction = TRUE;
// pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
// dElasity = (float)1.4;
// fDoCollision = TRUE;
//}
else if ( iCollisionCode == COLLISION_STRUCTURE_Z )
{
if ( CheckForCatcher( pObject, usStructureID ) )
{
return( FALSE );
}
CheckForObjectHittingMerc( pObject, usStructureID );
vTemp.x = 0;
vTemp.y = 0;
vTemp.z = -1;
pObject->fApplyFriction = TRUE;
pObject->AppliedMu = (float)(0.54 * TIME_MULTI );
dElasity = (float)1.2;
fDoCollision = TRUE;
}
else if ( iCollisionCode == COLLISION_WALL_SOUTHEAST || iCollisionCode == COLLISION_WALL_SOUTHWEST ||
iCollisionCode == COLLISION_WALL_NORTHEAST || iCollisionCode == COLLISION_WALL_NORTHWEST )
{
// A wall, do stuff
vTemp.x = dNormalX;
vTemp.y = dNormalY;
vTemp.z = dNormalZ;
fDoCollision = TRUE;
dElasity = (float)1.1;
}
else
{
vector_3 vIncident;
if ( CheckForCatcher( pObject, usStructureID ) )
{
return( FALSE );
}
CheckForObjectHittingMerc( pObject, usStructureID );
vIncident.x = dDeltaX;
vIncident.y = dDeltaY;
vIncident.z = 0;
// Nomralize
vIncident = VGetNormal( &vIncident );
//vTemp.x = -1;
//vTemp.y = 0;
//vTemp.z = 0;
vTemp.x = -1 * vIncident.x;
vTemp.y = -1 * vIncident.y;
vTemp.z = 0;
fDoCollision = TRUE;
dElasity = (float)1.1;
}
if ( fDoCollision )
{
pObject->CollisionNormal.x = vTemp.x;
pObject->CollisionNormal.y = vTemp.y;
pObject->CollisionNormal.z = vTemp.z;
pObject->CollisionElasticity = dElasity;
pObject->iOldCollisionCode = iCollisionCode;
// Save collision velocity
pObject->CollisionVelocity = VSetEqual( &(pObject->OldVelocity) );
if ( pObject->fPotentialForDebug )
{
PhysicsDebugMsg( String( "Object %d: Collision %d", pObject->iID, iCollisionCode ) );
PhysicsDebugMsg( String( "Object %d: Collision Normal %f %f %f", pObject->iID, vTemp.x, vTemp.y, vTemp.z ) );
PhysicsDebugMsg( String( "Object %d: Collision OldPos %f %f %f", pObject->iID, pObject->Position.x, pObject->Position.y, pObject->Position.z ) );
PhysicsDebugMsg( String( "Object %d: Collision Velocity %f %f %f", pObject->iID, pObject->CollisionVelocity.x, pObject->CollisionVelocity.y, pObject->CollisionVelocity.z ) );
}
pObject->fColliding = TRUE;
}
else
{
pObject->fColliding = FALSE;
pObject->sConsecutiveCollisions = 0;
pObject->sConsecutiveZeroVelocityCollisions = 0;
pObject->fHaveHitGround = FALSE;
}
}
return( fDoCollision );
}
void PhysicsResolveCollision( REAL_OBJECT *pObject, vector_3 *pVelocity, vector_3 *pNormal, real CoefficientOfRestitution )
{
real ImpulseNumerator, Impulse;
vector_3 vTemp;
ImpulseNumerator = -1 * CoefficientOfRestitution * VDotProduct( pVelocity , pNormal );
Impulse = ImpulseNumerator;
vTemp = VMultScalar( pNormal, Impulse );
pObject->Velocity = VAdd( &(pObject->Velocity), &vTemp );
}
BOOLEAN PhysicsMoveObject( REAL_OBJECT *pObject )
{
LEVELNODE *pNode;
INT32 sNewGridNo, sTileIndex;
ETRLEObject *pTrav;
HVOBJECT hVObject;
//Determine new gridno
sNewGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
if ( pObject->fFirstTimeMoved )
{
pObject->fFirstTimeMoved = FALSE;
pObject->sFirstGridNo = sNewGridNo;
}
// CHECK FOR RANGE< IF INVALID, REMOVE!
if ( sNewGridNo == -1 )
{
PhysicsDeleteObject( pObject );
return( FALSE );
}
// Look at old gridno
if ( sNewGridNo != pObject->sGridNo || pObject->pNode == NULL )
{
if ( pObject->fVisible )
{
if ( CheckForCatchObject( pObject ) )
{
pObject->fVisible = FALSE;
}
}
if ( pObject->fVisible )
{
// Add smoke trails...
// if ( pObject->Obj.usItem == MORTAR_SHELL && pObject->uiNumTilesMoved > 2 && pObject->ubActionCode == THROW_ARM_ITEM )
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 1147"));
if ( Item[pObject->Obj.usItem].usItemClass == IC_BOMB && pObject->uiNumTilesMoved > 2 && pObject->ubActionCode == THROW_ARM_ITEM )
{
if ( sNewGridNo != pObject->sGridNo )
{
ANITILE_PARAMS AniParams;
AniParams.sGridNo = sNewGridNo;
AniParams.ubLevelID = ANI_STRUCT_LEVEL;
AniParams.sDelay = (INT16)( 100 + PreRandom( 100 ) );
AniParams.sStartFrame = 0;
AniParams.uiFlags = ANITILE_CACHEDTILE | ANITILE_FORWARD | ANITILE_ALWAYS_TRANSLUCENT;
AniParams.sX = (INT16)pObject->Position.x;
AniParams.sY = (INT16)pObject->Position.y;
AniParams.sZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
strcpy( AniParams.zCachedFile, "TILECACHE\\MSLE_SMK.STI" );
CreateAnimationTile( &AniParams );
}
}
else if ( pObject->uiNumTilesMoved > 0 )
{
if ( sNewGridNo != pObject->sGridNo )
{
// We're at a new gridno!
if ( pObject->pNode != NULL )
{
RemoveStructFromLevelNode( pObject->sLevelNodeGridNo, pObject->pNode );
}
// We're at a new gridno!
if ( pObject->pShadow != NULL )
{
RemoveShadowFromLevelNode( pObject->sLevelNodeGridNo, pObject->pShadow );
}
// Now get graphic index
sTileIndex = GetTileGraphicForItem( &(Item[ pObject->Obj.usItem ] ) );
//sTileIndex = BULLETTILE1;
// Set new gridno, add
pNode = AddStructToTail( sNewGridNo, sTileIndex );
pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->uiFlags |= ( LEVELNODE_USEABSOLUTEPOS | LEVELNODE_IGNOREHEIGHT | LEVELNODE_PHYSICSOBJECT | LEVELNODE_DYNAMIC );
// Set levelnode
pObject->pNode = pNode;
// Add shadow
AddShadowToHead( sNewGridNo, sTileIndex );
pNode = gpWorldLevelData[ sNewGridNo ].pShadowHead;
pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->uiFlags |= ( LEVELNODE_USEABSOLUTEPOS | LEVELNODE_IGNOREHEIGHT | LEVELNODE_PHYSICSOBJECT | LEVELNODE_DYNAMIC );
// Set levelnode
pObject->pShadow = pNode;
pObject->sLevelNodeGridNo = sNewGridNo;
}
}
}
else
{
// Remove!
if ( pObject->pNode != NULL )
{
RemoveStructFromLevelNode( pObject->sLevelNodeGridNo, pObject->pNode );
}
// We're at a new gridno!
if ( pObject->pShadow != NULL )
{
RemoveShadowFromLevelNode( pObject->sLevelNodeGridNo, pObject->pShadow );
}
pObject->pNode = NULL;
pObject->pShadow = NULL;
}
if ( sNewGridNo != pObject->sGridNo )
{
pObject->uiNumTilesMoved++;
}
pObject->sGridNo = sNewGridNo;
if ( pObject->fPotentialForDebug )
{
PhysicsDebugMsg( String( "Object %d: uiNumTilesMoved: %d", pObject->iID, pObject->uiNumTilesMoved ) );
}
}
if ( pObject->fVisible )
{
// if ( pObject->Obj.usItem != MORTAR_SHELL || pObject->ubActionCode != THROW_ARM_ITEM )
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 1238"));
if ( !(Item[pObject->Obj.usItem].usItemClass == IC_BOMB) || pObject->ubActionCode != THROW_ARM_ITEM )
{
if ( pObject->pNode != NULL )
{
// OK, get offsets
hVObject = gTileDatabase[ pObject->pNode->usIndex ].hTileSurface;
pTrav = &(hVObject->pETRLEObject[ gTileDatabase[ pObject->pNode->usIndex ].usRegionIndex ] );
// Add new object / update position
// Update position data
pObject->pNode->sRelativeX = (INT16)pObject->Position.x; // + pTrav->sOffsetX;
pObject->pNode->sRelativeY = (INT16)pObject->Position.y; // + pTrav->sOffsetY;
pObject->pNode->sRelativeZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
// Update position data
pObject->pShadow->sRelativeX = (INT16)pObject->Position.x; // + pTrav->sOffsetX;
pObject->pShadow->sRelativeY = (INT16)pObject->Position.y; // + pTrav->sOffsetY;
pObject->pShadow->sRelativeZ = (INT16)gpWorldLevelData[ pObject->sGridNo ].sHeight;
}
}
}
return( TRUE );
}
#if 0
{
LEVELNODE *pNode;
INT32 sNewGridNo;
//Determine new gridno
sNewGridNo = MAPROWCOLTOPOS( ( pObject->Position.y / CELL_Y_SIZE ), ( pObject->Position.x / CELL_X_SIZE ) );
// Look at old gridno
if ( sNewGridNo != pObject->sGridNo )
{
// We're at a new gridno!
// First find levelnode of our object and delete
pNode = gpWorldLevelData[ pObject->sGridNo ].pStructHead;
// LOOP THORUGH OBJECT LAYER
while( pNode != NULL )
{
if ( pNode->uiFlags & LEVELNODE_PHYSICSOBJECT )
{
if ( pNode->iPhysicsObjectID == pObject->iID )
{
RemoveStruct( pObject->sGridNo, pNode->usIndex );
break;
}
}
pNode = ppNode->pNext;
}
// Set new gridno, add
}
// Add new object / update position
}
#endif
void ObjectHitWindow( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce )
{
EV_S_WINDOWHIT SWindowHit;
SWindowHit.sGridNo = sGridNo;
SWindowHit.usStructureID = usStructureID;
SWindowHit.fBlowWindowSouth = fBlowWindowSouth;
SWindowHit.fLargeForce = fLargeForce;
//AddGameEvent( S_WINDOWHIT, 0, &SWindowHit );
WindowHit( sGridNo, usStructureID, fBlowWindowSouth, fLargeForce );
}
vector_3 FindBestForceForTrajectory( INT32 sSrcGridNo, INT32 sGridNo,INT16 sStartZ, INT16 sEndZ, real dzDegrees, OBJECTTYPE *pItem, INT32 *psGridNo, real *pdMagForce )
{
vector_3 vDirNormal, vPosition, vForce;
INT16 sDestX, sDestY, sSrcX, sSrcY;
real dForce = 20;
real dRange;
real dPercentDiff = 0;
real dTestRange, dTestDiff;
INT32 iNumChecks = 0;
// Get XY from gridno
ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
ConvertGridNoToCenterCellXY( sSrcGridNo, &sSrcX, &sSrcY );
// Set position
vPosition.x = sSrcX;
vPosition.y = sSrcY;
vPosition.z = sStartZ;
// OK, get direction normal
vDirNormal.x = (float)(sDestX - sSrcX);
vDirNormal.y = (float)(sDestY - sSrcY);
vDirNormal.z = 0;
// NOmralize
vDirNormal = VGetNormal( &vDirNormal );
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dzDegrees );
// Get range
dRange = (float)GetRangeInCellCoordsFromGridNoDiff( sGridNo, sSrcGridNo );
//calculate force needed
dForce = (float)( 12.0 * ( sqrt( ( GRAVITY * dRange ) / sin( 2.0 * dzDegrees ) ) ) );
do
{
// This first force is just an estimate...
// now di a binary search to find best value....
iNumChecks++;
// Now use a force
vForce.x = dForce * vDirNormal.x;
vForce.y = dForce * vDirNormal.y;
vForce.z = dForce * vDirNormal.z;
dTestRange = CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
// What's the diff?
dTestDiff = dTestRange - dRange;
// How have we done?
// < 5% off...
if ( fabs( ( dTestDiff / dRange ) ) < .01 )
{
break;
}
if ( iNumChecks > MAX_INTEGRATIONS )
{
break;
}
// What is the Percentage difference?
dPercentDiff = dForce * ( dTestDiff / dRange );
// Adjust force accordingly
dForce = dForce - ( ( dPercentDiff ) / 2 );
} while( TRUE );
if ( pdMagForce )
{
(*pdMagForce) = dForce;
}
#ifdef JA2TESTVERSION
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Number of integration: %d", iNumChecks );
#endif
return( vForce );
}
INT32 FindFinalGridNoGivenDirectionGridNoForceAngle( INT32 sSrcGridNo, INT32 sGridNo, INT16 sStartZ, INT16 sEndZ, real dForce, real dzDegrees, OBJECTTYPE *pItem )
{
vector_3 vDirNormal, vPosition, vForce;
INT16 sDestX, sDestY, sSrcX, sSrcY;
real dRange;
INT32 sEndGridNo;
// Get XY from gridno
ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
ConvertGridNoToCenterCellXY( sSrcGridNo, &sSrcX, &sSrcY );
// Set position
vPosition.x = sSrcX;
vPosition.y = sSrcY;
vPosition.z = sStartZ;
// OK, get direction normal
vDirNormal.x = (float)(sDestX - sSrcX);
vDirNormal.y = (float)(sDestY - sSrcY);
vDirNormal.z = 0;
// NOmralize
vDirNormal = VGetNormal( &vDirNormal );
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dzDegrees );
// Get range
dRange = (float)GetRangeInCellCoordsFromGridNoDiff( sGridNo, sSrcGridNo );
// Now use a force
vForce.x = dForce * vDirNormal.x;
vForce.y = dForce * vDirNormal.y;
vForce.z = dForce * vDirNormal.z;
CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, &sEndGridNo );
return( sEndGridNo );
}
real FindBestAngleForTrajectory( INT32 sSrcGridNo, INT32 sGridNo,INT16 sStartZ, INT16 sEndZ, real dForce, OBJECTTYPE *pItem, INT32 *psGridNo )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory");
vector_3 vDirNormal, vPosition, vForce;
INT16 sDestX, sDestY, sSrcX, sSrcY;
real dRange;
real dzDegrees = ( (float)PI/8 );
real dPercentDiff = 0;
real dTestRange, dTestDiff;
INT32 iNumChecks = 0;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory: get range and direction");
// Get XY from gridno
ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
ConvertGridNoToCenterCellXY( sSrcGridNo, &sSrcX, &sSrcY );
// Set position
vPosition.x = sSrcX;
vPosition.y = sSrcY;
vPosition.z = sStartZ;
// OK, get direction normal
vDirNormal.x = (float)(sDestX - sSrcX);
vDirNormal.y = (float)(sDestY - sSrcY);
vDirNormal.z = 0;
// NOmralize
vDirNormal = VGetNormal( &vDirNormal );
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dzDegrees );
// Get range
dRange = (float)GetRangeInCellCoordsFromGridNoDiff( sGridNo, sSrcGridNo );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory: find best direction");
do
{
// This first direction is just an estimate...
// now do a binary search to find best value....
iNumChecks++;
// Now use a force
vForce.x = dForce * vDirNormal.x;
vForce.y = dForce * vDirNormal.y;
vForce.z = dForce * vDirNormal.z;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory: CalculateObjectTrajectory");
dTestRange = CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
// What's the diff?
dTestDiff = dTestRange - dRange;
// How have we done?
// < 5% off...
if ( fabs( (FLOAT)( dTestDiff / dRange ) ) < .05 )
{
break;
}
if ( iNumChecks > MAX_INTEGRATIONS )
{
break;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory: get percentage difference");
// What is the Percentage difference?
dPercentDiff = dzDegrees * ( dTestDiff / dRange );
// Adjust degrees accordingly
dzDegrees = dzDegrees - ( dPercentDiff / 2 );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory: check angle");
// OK, If our angle is too far either way, giveup!
if ( fabs( dzDegrees ) >= ( PI / 2 ) || fabs( dzDegrees ) <= 0.005 )
{
// Use 0.....
dzDegrees = 0;
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dzDegrees );
// Now use a force
vForce.x = dForce * vDirNormal.x;
vForce.y = dForce * vDirNormal.y;
vForce.z = dForce * vDirNormal.z;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory: giving up CalculateObjectTrajectory");
dTestRange = CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory: giving up returning 0");
return( (FLOAT)( dzDegrees ) );
}
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dzDegrees );
} while( TRUE );
// OK, we have our force, calculate change to get through without collide
//if ( ChanceToGetThroughObjectTrajectory( sEndZ, pItem, &vPosition, &vForce ) == 0 )
//{
// ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Chance to get through throw is 0." );
//}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"FindBestAngleForTrajectory done");
return( dzDegrees );
}
void FindTrajectory( INT32 sSrcGridNo, INT32 sGridNo, INT16 sStartZ, INT16 sEndZ, real dForce, real dzDegrees, OBJECTTYPE *pItem, INT32 *psGridNo )
{
vector_3 vDirNormal, vPosition, vForce;
INT16 sDestX, sDestY, sSrcX, sSrcY;
// Get XY from gridno
ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
ConvertGridNoToCenterCellXY( sSrcGridNo, &sSrcX, &sSrcY );
// Set position
vPosition.x = sSrcX;
vPosition.y = sSrcY;
vPosition.z = sStartZ;
// OK, get direction normal
vDirNormal.x = (float)(sDestX - sSrcX);
vDirNormal.y = (float)(sDestY - sSrcY);
vDirNormal.z = 0;
// NOmralize
vDirNormal = VGetNormal( &vDirNormal );
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dzDegrees );
// Now use a force
vForce.x = dForce * vDirNormal.x;
vForce.y = dForce * vDirNormal.y;
vForce.z = dForce * vDirNormal.z;
CalculateObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psGridNo );
}
// OK, this will, given a target Z, INVTYPE, source, target gridnos, initial force vector, will
// return range
FLOAT CalculateObjectTrajectory( INT16 sTargetZ, OBJECTTYPE *pItem, vector_3 *vPosition, vector_3 *vForce, INT32 *psFinalGridNo )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateObjectTrajectory");
INT32 iID;
REAL_OBJECT *pObject;
FLOAT dDiffX, dDiffY;
INT32 sGridNo;
//int cnt=0;
if ( psFinalGridNo )
{
(*psFinalGridNo) = NOWHERE;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateObjectTrajectory: createphysicalobject");
// OK, create a physics object....
iID = CreatePhysicalObject( pItem, -1, vPosition->x, vPosition->y, vPosition->z, vForce->x, vForce->y, vForce->z, NOBODY, NO_THROW_ACTION, 0, TEST_OBJECT_NO_COLLISIONS );
if ( iID == -1 )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateObjectTrajectory: returning -1");
return( -1 );
}
pObject = &( ObjectSlots[ iID ] );
// Set some special values...
pObject->fTestObject = TEST_OBJECT_NO_COLLISIONS;
pObject->TestZTarget = sTargetZ;
pObject->fTestPositionNotSet = TRUE;
pObject->fVisible = FALSE;
// Alrighty, move this beast until it dies....
while( pObject->fAlive )
{
//cnt = cnt + 1;
// DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateObjectTrajectory: calling simulateobject - this object never fucking dies!!!!");
SimulateObject( pObject, (float)DELTA_T );
}
// Calculate gridno from last position
sGridNo = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateObjectTrajectory: physicsdeleteobject");
PhysicsDeleteObject( pObject );
// get new x, y, z values
dDiffX = ( pObject->TestTargetPosition.x - vPosition->x );
dDiffY = ( pObject->TestTargetPosition.y - vPosition->y );
if ( psFinalGridNo )
{
(*psFinalGridNo) = sGridNo;
}
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateObjectTrajectory: done");
return( (FLOAT)sqrt( ( dDiffX * dDiffX ) + ( dDiffY * dDiffY ) ) );
}
INT32 ChanceToGetThroughObjectTrajectory( INT16 sTargetZ, OBJECTTYPE *pItem, vector_3 *vPosition, vector_3 *vForce, INT32 *psNewGridNo, INT8 *pbLevel, BOOLEAN fFromUI )
{
INT32 iID;
REAL_OBJECT *pObject;
// OK, create a physics object....
iID = CreatePhysicalObject( pItem, -1, vPosition->x, vPosition->y, vPosition->z, vForce->x, vForce->y, vForce->z, NOBODY, NO_THROW_ACTION, 0, TEST_OBJECT_NOTWALLROOF_COLLISIONS );
if ( iID == -1 )
{
return( -1 );
}
pObject = &( ObjectSlots[ iID ] );
// Set some special values...
pObject->fTestObject = TEST_OBJECT_NOTWALLROOF_COLLISIONS;
pObject->fTestPositionNotSet = TRUE;
pObject->TestZTarget = sTargetZ;
pObject->fVisible = FALSE;
//pObject->fPotentialForDebug = TRUE;
// Alrighty, move this beast until it dies....
while( pObject->fAlive )
{
SimulateObject( pObject, (float)DELTA_T );
}
if ( psNewGridNo != NULL )
{
// Calculate gridno from last position
// If NOT from UI, use exact collision position
// JA25 comment from CJC: testing breaklights, we ran into a situation where EndedWithCollisionPosition was not
// set due to breaklight stopping on flat ground. It makes sense that if EndedWithCollisionPosition is
// unset, at least the Position value is of SOME use...
if ( fFromUI || (pObject->EndedWithCollisionPosition.x == 0.0f && pObject->EndedWithCollisionPosition.y == 0.0f) )
{
(*psNewGridNo) = MAPROWCOLTOPOS( ( (INT16)pObject->Position.y / CELL_Y_SIZE ), ( (INT16)pObject->Position.x / CELL_X_SIZE ) );
}
else
{
(*psNewGridNo) = MAPROWCOLTOPOS( ( (INT16)pObject->EndedWithCollisionPosition.y / CELL_Y_SIZE ), ( (INT16)pObject->EndedWithCollisionPosition.x / CELL_X_SIZE ) );
}
(*pbLevel) = GET_OBJECT_LEVEL( pObject->EndedWithCollisionPosition.z - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ (*psNewGridNo) ].sHeight ) );
}
PhysicsDeleteObject( pObject );
// See If we collided
if ( pObject->fTestEndedWithCollision )
{
return( 0 );
}
return( 100 );
}
FLOAT CalculateLaunchItemAngle( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubHeight, real dForce, OBJECTTYPE *pItem, INT32 *psGridNo )
{
real dAngle;
INT16 sSrcX, sSrcY;
ConvertGridNoToCenterCellXY( pSoldier->sGridNo, &sSrcX, &sSrcY );
dAngle = FindBestAngleForTrajectory( pSoldier->sGridNo, sGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->pathing.bLevel ), ubHeight, dForce, pItem, psGridNo );
// new we have defaut angle value...
return( dAngle );
}
void CalculateLaunchItemBasicParams( SOLDIERTYPE *pSoldier, OBJECTTYPE *pItem, INT32 sGridNo, UINT8 ubLevel, INT16 sEndZ, FLOAT *pdMagForce, FLOAT *pdDegrees, INT32 *psFinalGridNo, BOOLEAN fArmed )
{
INT32 sInterGridNo = NOWHERE;
INT16 sStartZ;
FLOAT dMagForce, dMaxForce, dMinForce;
FLOAT dDegrees, dNewDegrees;
BOOLEAN fThroughIntermediateGridNo = FALSE;
UINT16 usLauncher;
BOOLEAN fIndoors = FALSE;
BOOLEAN fLauncher = FALSE;
BOOLEAN fMortar = FALSE;
BOOLEAN fGLauncher = FALSE;
INT16 sMinRange = 0;
// Start with default degrees/ force
dDegrees = OUTDOORS_START_ANGLE;
sStartZ = GET_SOLDIER_THROW_HEIGHT( pSoldier->pathing.bLevel );
// Are we armed, and are we throwing a LAUNCHABLE?
//MM: Again, this only works if the launchers are all the same
//usLauncher = GetLauncherFromLaunchable( pItem->usItem );
//MM: Replacement:
OBJECTTYPE *pObj = NULL;
usLauncher = 0;
pObj = pSoldier->GetUsedWeapon( &pSoldier->inv[pSoldier->ubAttackingHand] );
if ( pObj != NULL )
{
if (Item[pObj->usItem].usItemClass == IC_LAUNCHER)
usLauncher = pObj->usItem;
else if (Item[pObj->usItem].usItemClass == IC_GUN)
{
usLauncher = GetAttachedGrenadeLauncher(pObj);
}
}
if ( usLauncher == 0) // fail back to the original
usLauncher = GetLauncherFromLaunchable( pItem->usItem );
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 1741"));
if ( fArmed && ( Item[usLauncher].mortar || Item[pItem->usItem].mortar ) )
{
// Start at 0....
sStartZ = ( pSoldier->pathing.bLevel * 256 );
fMortar = TRUE;
sMinRange = MIN_MORTAR_RANGE;
//fLauncher = TRUE;
}
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 1750"));
if ( fArmed && ( Item[usLauncher].grenadelauncher || Item[pItem->usItem].grenadelauncher ) )
{
// OK, look at target level and decide angle to use...
if ( ubLevel == 1 )
{
dDegrees = GLAUNCHER_HIGHER_LEVEL_START_ANGLE;
}
else
{
dDegrees = GLAUNCHER_START_ANGLE;
}
fGLauncher = TRUE;
sMinRange = MIN_MORTAR_RANGE/2;
if ( (gGameSettings.fOptions[TOPTION_GL_HIGH_ANGLE] && pSoldier->bTeam == OUR_TEAM) || ( pSoldier->bTeam == ENEMY_TEAM && gGameOptions.ubDifficultyLevel >= DIF_LEVEL_HARD ) )
dDegrees *= 2;
//fLauncher = TRUE;
}
// CHANGE DEGREE VALUES BASED ON IF WE ARE INSIDE, ETC
// ARE WE INSIDE?
if ( gfCaves || gfBasement )
{
// Adjust angle....
dDegrees = INDOORS_START_ANGLE;
fIndoors = TRUE;
}
if ( ( IsRoofPresentAtGridNo( pSoldier->sGridNo ) ) && pSoldier->pathing.bLevel == 0 )
{
// Adjust angle....
dDegrees = INDOORS_START_ANGLE;
fIndoors = TRUE;
}
// IS OUR TARGET INSIDE?
if ( IsRoofPresentAtGridNo( sGridNo ) && ubLevel == 0 )
{
// Adjust angle....
dDegrees = INDOORS_START_ANGLE;
fIndoors = TRUE;
}
// OK, look if we can go through a windows here...
if ( ubLevel == 0 )
{
sInterGridNo = SoldierToLocationWindowTest( pSoldier, sGridNo );
// Lesh: fixing nasty freeze when throwing item through window
// sometimes SoldierToLocationWindowTest returns same value as pSoldier->sGridNo
// this leads to fault in FindBestAngleForTrajectory function and it subroutines loops
// supplied by invalid data
if ( pSoldier->sGridNo == sInterGridNo )
{
// bad news - i can't throw item at myself
// so use dir incrementer
UINT8 ubDir = atan8( CenterX(pSoldier->sGridNo), CenterY(pSoldier->sGridNo), CenterX(sGridNo), CenterY(sGridNo) );
sInterGridNo += DirIncrementer[ubDir];
}
}
else
{
sInterGridNo = NOWHERE;
}
if (!TileIsOutOfBounds(sInterGridNo))
{
// IF so, adjust target height, gridno....
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Through a window!" );
fThroughIntermediateGridNo = TRUE;
}
if ( !fLauncher )
{
// Find force for basic
FindBestForceForTrajectory( pSoldier->sGridNo, sGridNo, sStartZ, sEndZ, dDegrees, pItem, psFinalGridNo, &dMagForce );
// Adjust due to max range....
dMaxForce = CalculateSoldierMaxForce( pSoldier, dDegrees, pItem, fArmed );
if ( fIndoors )
{
dMaxForce = dMaxForce * 2;
}
if ( dMagForce > dMaxForce )
{
dMagForce = dMaxForce;
}
// ATE: If we are a mortar, make sure we are at min.
if ( fMortar || fGLauncher )
{
// find min force
dMinForce = CalculateForceFromRange( pItem->usItem, (INT16)( sMinRange / 10 ), (FLOAT)( PI / 4 ) );
if ( dMagForce < dMinForce )
{
dMagForce = dMinForce;
}
}
if ( fThroughIntermediateGridNo )
{
// Given this power, now try and go through this window....
dDegrees = FindBestAngleForTrajectory( pSoldier->sGridNo, sInterGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->pathing.bLevel ), 150, dMagForce, pItem, psFinalGridNo );
}
}
else
{
// Use MAX force, vary angle....
dMagForce = CalculateSoldierMaxForce( pSoldier, dDegrees, pItem, fArmed );
if ( ubLevel == 0 )
{
dMagForce = (float)( dMagForce * 1.25 );
}
FindTrajectory( pSoldier->sGridNo, sGridNo, sStartZ, sEndZ, dMagForce, dDegrees, pItem, psFinalGridNo );
if ( ubLevel == 1 && !fThroughIntermediateGridNo )
{
// Is there a guy here...?
if ( WhoIsThere2( sGridNo, ubLevel ) != NOBODY )
{
dMagForce = (float)( dMagForce * 0.85 );
// Yep, try to get angle...
dNewDegrees = FindBestAngleForTrajectory( pSoldier->sGridNo, sGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->pathing.bLevel ), 150, dMagForce, pItem, psFinalGridNo );
if ( dNewDegrees != 0 )
{
dDegrees = dNewDegrees;
}
}
}
if ( fThroughIntermediateGridNo )
{
dDegrees = FindBestAngleForTrajectory( pSoldier->sGridNo, sInterGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->pathing.bLevel ), 150, dMagForce, pItem, psFinalGridNo );
}
}
(*pdMagForce) = dMagForce;
(*pdDegrees ) = dDegrees;
}
BOOLEAN CalculateLaunchItemChanceToGetThrough( SOLDIERTYPE *pSoldier, OBJECTTYPE *pItem, INT32 sGridNo, UINT8 ubLevel, INT16 sEndZ, INT32 *psFinalGridNo, BOOLEAN fArmed, INT8 *pbLevel, BOOLEAN fFromUI )
{
FLOAT dForce, dDegrees;
INT16 sDestX, sDestY, sSrcX, sSrcY;
vector_3 vForce, vPosition, vDirNormal;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateLaunchItemChanceToGetThrough");
if ( pSoldier->sGridNo == sGridNo )
{
printf("Warning! Soldier #%d attempted to launch item at himself\n", pSoldier->ubID);
return FALSE;
}
// Ge7t basic launch params...
CalculateLaunchItemBasicParams( pSoldier, pItem, sGridNo, ubLevel, sEndZ, &dForce, &dDegrees, psFinalGridNo, fArmed );
// Get XY from gridno
ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
ConvertGridNoToCenterCellXY( pSoldier->sGridNo, &sSrcX, &sSrcY );
// Set position
vPosition.x = sSrcX;
vPosition.y = sSrcY;
vPosition.z = GET_SOLDIER_THROW_HEIGHT( pSoldier->pathing.bLevel );
// OK, get direction normal
vDirNormal.x = (float)(sDestX - sSrcX);
vDirNormal.y = (float)(sDestY - sSrcY);
vDirNormal.z = 0;
// NOmralize
vDirNormal = VGetNormal( &vDirNormal );
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dDegrees );
// Do force....
vForce.x = dForce * vDirNormal.x;
vForce.y = dForce * vDirNormal.y;
vForce.z = dForce * vDirNormal.z;
// OK, we have our force, calculate change to get through without collide
if ( ChanceToGetThroughObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, psFinalGridNo, pbLevel, fFromUI ) == 0 )
{
return( FALSE );
}
if ( (*pbLevel) != ubLevel )
{
return( FALSE );
}
if ( !fFromUI && (*psFinalGridNo) != sGridNo )
{
return( FALSE );
}
return( TRUE );
}
FLOAT CalculateForceFromRange(UINT16 usItem, INT16 sRange, FLOAT dDegrees )
{
FLOAT dMagForce;
INT32 sSrcGridNo, sDestGridNo;
INT32 sFinalGridNo;
// WANNE: This does not work, so I reverted back!
// SB: Now this works on all map sizes, I've tested it!
// OK, use a fake gridno, find the new gridno based on range, use height of merc, end height of ground,
// 45 degrees
sSrcGridNo = INT32(WORLD_COLS/2+double(WORLD_COLS)*WORLD_ROWS/5.914);
sDestGridNo = sSrcGridNo + ( sRange * WORLD_COLS );
// sSrcGridNo = 4408;
// sDestGridNo = 4408 + ( sRange * WORLD_COLS );
// Buggler: impact explosives requiring larger force to reach desired range due to no bounce
// Please change the if conditions too when definition of OBJECT_DETONATE_ON_IMPACT( object ) changes
if ( ( Item[ usItem ].usItemClass == IC_BOMB ) || ( Explosive[ Item[ usItem ].ubClassIndex ].fExplodeOnImpact ) ) // && ( object->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) )
// Use a mortar shell objecttype to simulate impact explosives
CreateItem( MORTAR_SHELL, 100, &gTempObject );
else
// Use a grenade objecttype to simulate bouncy explosives
CreateItem( HAND_GRENADE, 100, &gTempObject );
FindBestForceForTrajectory( sSrcGridNo, sDestGridNo, GET_SOLDIER_THROW_HEIGHT( 0 ), 0, dDegrees, &gTempObject, &sFinalGridNo, &dMagForce );
// WANNE: Problem is the FORCE!!
// dMagForce = 460.00;
return( dMagForce );
}
FLOAT CalculateSoldierMaxForce( SOLDIERTYPE *pSoldier, FLOAT dDegrees , OBJECTTYPE *pItem , BOOLEAN fArmed )
{
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateSoldierMaxForce");
INT32 uiMaxRange;
FLOAT dMagForce;
dDegrees = (FLOAT)( PI/4 );
uiMaxRange = CalcMaxTossRange( pSoldier, pItem->usItem, fArmed, pItem );
dMagForce = CalculateForceFromRange( pItem->usItem, (INT16) uiMaxRange, dDegrees );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateSoldierMaxForce: done");
return( dMagForce );
}
void CalculateLaunchItemParamsForThrow( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubLevel, INT16 sEndZ, OBJECTTYPE *pItem, INT8 bMissBy, UINT8 ubActionCode, UINT32 uiActionData )
{
FLOAT dForce, dDegrees;
INT16 sDestX, sDestY, sSrcX, sSrcY;
vector_3 vForce, vDirNormal;
INT32 sFinalGridNo;
BOOLEAN fArmed = FALSE;
UINT16 usLauncher;
INT16 sStartZ;
INT8 bMinMissRadius, bMaxMissRadius, bMaxRadius;
FLOAT fScale;
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateLaunchItemParamsForThrow");
// Set target ID if anyone
pSoldier->ubTargetID = WhoIsThere2( sGridNo, ubLevel );
if ( ubActionCode == THROW_ARM_ITEM )
{
fArmed = TRUE;
}
if ( bMissBy < 0 )
{
// then we hit!
bMissBy = 0;
// SANDRO - new merc records
if( pSoldier->bTeam == 0 && pSoldier->ubProfile != NO_PROFILE )
{
gMercProfiles[ pSoldier->ubProfile ].records.usShotsHit++;
}
}
//if ( 0 )
if ( bMissBy > 0 )
{
// Max the miss variance
if ( bMissBy > MAX_MISS_BY )
{
bMissBy = MAX_MISS_BY;
}
// Min the miss varience...
if ( bMissBy < MIN_MISS_BY )
{
bMissBy = MIN_MISS_BY;
}
// Adjust position, force, angle
#ifdef JA2TESTVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Throw miss by: %d", bMissBy );
#endif
// Default to max radius...
bMaxRadius = 5;
// scale if pyth spaces away is too far
if ( PythSpacesAway( sGridNo, pSoldier->sGridNo ) < ( (float)bMaxRadius / (float)1.5 ) )
{
bMaxRadius = PythSpacesAway( sGridNo, pSoldier->sGridNo ) / 2;
}
// Get radius
fScale = ( (float)bMissBy / (float) MAX_MISS_BY );
bMaxMissRadius = (INT8)( bMaxRadius * fScale );
// Limit max radius...
if ( bMaxMissRadius > 4 )
{
bMaxMissRadius = 4;
}
bMinMissRadius = bMaxMissRadius - 1;
if ( bMinMissRadius < 2 )
{
bMinMissRadius = 2;
}
if ( bMaxMissRadius < bMinMissRadius )
{
bMaxMissRadius = bMinMissRadius;
}
sGridNo = RandomGridFromRadius( sGridNo, bMinMissRadius, bMaxMissRadius );
}
// Get basic launch params...
CalculateLaunchItemBasicParams( pSoldier, pItem, sGridNo, ubLevel, sEndZ, &dForce, &dDegrees, &sFinalGridNo, fArmed );
// Get XY from gridno
ConvertGridNoToCenterCellXY( sGridNo, &sDestX, &sDestY );
ConvertGridNoToCenterCellXY( pSoldier->sGridNo, &sSrcX, &sSrcY );
// OK, get direction normal
vDirNormal.x = (float)(sDestX - sSrcX);
vDirNormal.y = (float)(sDestY - sSrcY);
vDirNormal.z = 0;
// NOmralize
vDirNormal = VGetNormal( &vDirNormal );
// From degrees, calculate Z portion of normal
vDirNormal.z = (float)sin( dDegrees );
// Do force....
vForce.x = dForce * vDirNormal.x;
vForce.y = dForce * vDirNormal.y;
vForce.z = dForce * vDirNormal.z;
// Allocate Throw Parameters
pSoldier->pThrowParams = (THROW_PARAMS *) MemAlloc( sizeof( THROW_PARAMS ) );
memset( pSoldier->pThrowParams, 0, sizeof( THROW_PARAMS ) );
OBJECTTYPE::CopyToOrCreateAt( &pSoldier->pTempObject, pItem);
pSoldier->pThrowParams->dX = (float)sSrcX;
pSoldier->pThrowParams->dY = (float)sSrcY;
sStartZ = GET_SOLDIER_THROW_HEIGHT( pSoldier->pathing.bLevel );
//MM: Again, this only works if the launchers are all the same
//usLauncher = GetLauncherFromLaunchable( pItem->usItem );
//MM: Replacement:
OBJECTTYPE *pObj = NULL;
usLauncher = 0;
pObj = pSoldier->GetUsedWeapon( &pSoldier->inv[pSoldier->ubAttackingHand] );
if ( pObj != NULL )
{
if (Item[pObj->usItem].usItemClass == IC_LAUNCHER)
usLauncher = pObj->usItem;
else if (Item[pObj->usItem].usItemClass == IC_GUN)
{
usLauncher = GetAttachedGrenadeLauncher(pObj);
}
}
if ( usLauncher == 0) // fail back to the original
usLauncher = GetLauncherFromLaunchable( pItem->usItem );
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 2103"));
if ( fArmed && Item[usLauncher].mortar )
{
// Start at 0....
sStartZ = ( pSoldier->pathing.bLevel * 256 ) + 50;
}
pSoldier->pThrowParams->dZ = (float)sStartZ;
pSoldier->pThrowParams->dForceX = vForce.x;
pSoldier->pThrowParams->dForceY = vForce.y;
pSoldier->pThrowParams->dForceZ = vForce.z;
pSoldier->pThrowParams->dLifeSpan = -1;
pSoldier->pThrowParams->ubActionCode = ubActionCode;
pSoldier->pThrowParams->uiActionData = uiActionData;
// Dirty interface
DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 );
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateLaunchItemParamsForThrow done");
}
BOOLEAN CheckForCatcher( REAL_OBJECT *pObject, UINT16 usStructureID )
{
// Do we want to catch?
if ( pObject->fTestObject == NO_TEST_OBJECT )
{
if ( pObject->ubActionCode == THROW_TARGET_MERC_CATCH )
{
// Is it a guy?
if ( usStructureID < INVALID_STRUCTURE_ID )
{
//Is it the same guy?
if ( usStructureID == pObject->uiActionData )
{
if ( DoCatchObject( pObject ) )
{
pObject->fAlive = FALSE;
return( TRUE );
}
}
}
}
}
return( FALSE );
}
void CheckForObjectHittingMerc( REAL_OBJECT *pObject, UINT16 usStructureID )
{
SOLDIERTYPE *pSoldier;
INT16 sDamage, sBreath;
// Do we want to catch?
if ( pObject->fTestObject == NO_TEST_OBJECT )
{
// Is it a guy?
if ( usStructureID < INVALID_STRUCTURE_ID )
{
if ( pObject->ubLastTargetTakenDamage != (UINT8)usStructureID )
{
// Flugente: if this fails, something is very wrong indeed
Assert(usStructureID<TOTAL_SOLDIERS);
pSoldier = MercPtrs[ usStructureID ];
sDamage = 1;
sBreath = 0;
pSoldier->EVENT_SoldierGotHit( NOTHING, sDamage, sBreath, pSoldier->ubDirection, 0, pObject->ubOwner, FIRE_WEAPON_TOSSED_OBJECT_SPECIAL, 0, 0, NOWHERE );
pObject->ubLastTargetTakenDamage = (UINT8)( usStructureID );
}
}
}
}
BOOLEAN CheckForCatchObject( REAL_OBJECT *pObject )
{
SOLDIERTYPE *pSoldier;
UINT32 uiSpacesAway;
// Do we want to catch?
if ( pObject->fTestObject == NO_TEST_OBJECT )
{
if ( pObject->ubActionCode == THROW_TARGET_MERC_CATCH )
{
pSoldier = MercPtrs[ pObject->uiActionData ];
// Is it a guy?
// Are we close to this guy?
uiSpacesAway = PythSpacesAway( pObject->sGridNo, pSoldier->sGridNo );
if ( uiSpacesAway < 4 && !pObject->fAttemptedCatch )
{
if ( pSoldier->usAnimState != CATCH_STANDING &&
pSoldier->usAnimState != CATCH_CROUCHED &&
pSoldier->usAnimState != LOWER_RIFLE )
{
if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_STAND )
{
pSoldier->EVENT_InitNewSoldierAnim( CATCH_STANDING, 0 , FALSE );
}
else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_CROUCH )
{
pSoldier->EVENT_InitNewSoldierAnim( CATCH_CROUCHED, 0 , FALSE );
}
pObject->fCatchAnimOn = TRUE;
}
}
pObject->fAttemptedCatch = TRUE;
if ( uiSpacesAway <= 1 && !pObject->fCatchCheckDone )
{
if ( AttemptToCatchObject( pObject ) )
{
return( TRUE );
}
}
}
}
return( FALSE );
}
BOOLEAN AttemptToCatchObject( REAL_OBJECT *pObject )
{
SOLDIERTYPE *pSoldier;
UINT8 ubChanceToCatch;
// Get intended target
pSoldier = MercPtrs[ pObject->uiActionData ];
// OK, get chance to catch
// base it on...? CC? Dexterity?
ubChanceToCatch = 50 + EffectiveDexterity( pSoldier, FALSE ) / 2;
#ifdef JA2TESTVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Chance To Catch: %d", ubChanceToCatch );
#endif
pObject->fCatchCheckDone = TRUE;
if ( PreRandom( 100 ) > ubChanceToCatch )
{
return( FALSE );
}
pObject->fCatchGood = TRUE;
return( TRUE );
}
BOOLEAN DoCatchObject( REAL_OBJECT *pObject )
{
SOLDIERTYPE *pSoldier;
BOOLEAN fGoodCatch = FALSE;
UINT16 usItem;
// Get intended target
pSoldier = MercPtrs[ pObject->uiActionData ];
// Catch anim.....
switch( gAnimControl[ pSoldier->usAnimState ].ubHeight )
{
case ANIM_STAND:
pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
pSoldier->EVENT_InitNewSoldierAnim( END_CATCH, 0 , FALSE );
break;
case ANIM_CROUCH:
pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
pSoldier->EVENT_InitNewSoldierAnim( END_CROUCH_CATCH, 0 , FALSE );
break;
}
PlayJA2Sample( CATCH_OBJECT, RATE_11025, SoundVolume( MIDVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
pObject->fCatchAnimOn = FALSE;
if ( !pObject->fCatchGood )
{
return( FALSE );
}
// Get item
usItem = pObject->Obj.usItem;
// Transfer object
fGoodCatch = AutoPlaceObject( pSoldier, &(pObject->Obj), TRUE );
// Report success....
if ( fGoodCatch )
{
pObject->fDropItem = FALSE;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pMessageStrings[ MSG_MERC_CAUGHT_ITEM ], pSoldier->GetName(), ShortItemNames[ usItem ] );
}
return( TRUE );
}
//#define TESTDUDEXPLOSIVES
void HandleArmedObjectImpact( REAL_OBJECT *pObject )
{
INT16 sZ;
BOOLEAN fDoImpact = FALSE;
BOOLEAN fCheckForDuds = FALSE;
bool fIsDud = FALSE;
OBJECTTYPE *pObj;
INT32 iTrapped = 0;
UINT16 usFlags = 0;
INT8 bLevel = 0;
if (is_networked && is_client)
{
if (pObject->mpIsFromRemoteClient && !pObject->mpHaveClientResult)
{
return;
}
}
// Calculate pixel position of z
sZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)( pObject->Position.z ) ) - gpWorldLevelData[ pObject->sGridNo ].sHeight;
// get OBJECTTYPE
pObj = &(pObject->Obj);
// ATE: Make sure number of objects is 1...
pObj->ubNumberOfObjects = 1;
if ( Item[ pObj->usItem ].usItemClass & IC_GRENADE )
{
fCheckForDuds = TRUE;
}
// if ( pObj->usItem == MORTAR_SHELL )
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 2337"));
if (Item[ pObj->usItem ].usItemClass == IC_BOMB)
{
fCheckForDuds = TRUE;
}
if ( Item[ pObj->usItem ].usItemClass & IC_THROWN )
{
AddItemToPool( pObject->sGridNo, pObj, INVISIBLE, bLevel, usFlags, 0 );
}
if ( fCheckForDuds )
{
// OJW - 20021002 - MP Explosives
if (is_networked && is_client && pObject->mpIsFromRemoteClient && pObject->mpHaveClientResult)
{
fIsDud = pObject->mpWasDud;
}
else
{
// If we landed on anything other than the floor, always! go off...
#ifdef TESTDUDEXPLOSIVES
if ( sZ != 0 || pObject->fInWater )
#else
if ( sZ != 0 || pObject->fInWater || ( (*pObj)[0]->data.objectStatus >= USABLE && ( PreRandom( 100 ) < (UINT32) (*pObj)[0]->data.objectStatus + PreRandom( 50 ) ) ) )
#endif
{
fDoImpact = TRUE;
fIsDud = false;
}
else // didn't go off!
{
fIsDud = true;
}
}
if (fIsDud)
{
#ifdef TESTDUDEXPLOSIVES
if ( 1 )
#else
if ( (*pObj)[0]->data.objectStatus >= USABLE && PreRandom(100) < (UINT32) (*pObj)[0]->data.objectStatus + PreRandom( 50 ) )
#endif
{
iTrapped = PreRandom( 4 ) + 2;
}
if ( iTrapped )
{
// Start timed bomb...
usFlags |= WORLD_ITEM_ARMED_BOMB;
(*pObj)[0]->data.misc.bDetonatorType = BOMB_TIMED;
(*pObj)[0]->data.misc.bDelay = (INT8)( 1 + PreRandom( 2 ) );
}
// ATE: If we have collided with roof last...
if ( pObject->iOldCollisionCode == COLLISION_ROOF )
{
bLevel = 1;
}
// Add item to pool....
AddItemToPool( pObject->sGridNo, pObj, INVISIBLE, bLevel, usFlags, 0 );
// All teams lok for this...
NotifySoldiersToLookforItems( );
if ( pObject->ubOwner != NOBODY )
{
MercPtrs[ pObject->ubOwner ]->DoMercBattleSound( (INT8)( BATTLE_SOUND_CURSE1 ) );
}
}
}
else
{
fDoImpact = TRUE;
}
if ( fDoImpact )
{
if ( Item[pObject->Obj.usItem].flare )
{
//if the light object will ber created OFF the ground
if( pObject->Position.z > 0 && FindBuilding(pObject->sGridNo) )
{
//we cannot create the light source above the ground, or on a roof. The system doesnt support it.
AddItemToPool( pObject->sGridNo, &( pObject->Obj ), 1, 1, 0, -1 );
}
else
{
// Add a light effect...
NewLightEffect( pObject->sGridNo, (UINT8)Explosive[Item[pObject->Obj.usItem].ubClassIndex].ubDuration , (UINT8)Explosive[Item[pObject->Obj.usItem].ubClassIndex].ubStartRadius );
}
}
else if ( Item[ pObject->Obj.usItem ].usItemClass & IC_GRENADE )
{
/* ARM: Removed. Rewards even missed throws, and pulling a pin doesn't really teach anything about explosives
if ( MercPtrs[ pObject->ubOwner ]->bTeam == gbPlayerNum && gTacticalStatus.uiFlags & INCOMBAT )
{
// tossed grenade, not a dud, so grant xp
// EXPLOSIVES GAIN (10): Tossing grenade
if ( pObject->ubOwner != NOBODY )
{
StatChange( MercPtrs[ pObject->ubOwner ], EXPLODEAMT, 10, FALSE );
}
}
*/
IgniteExplosion( pObject->ubOwner, (INT16)pObject->Position.x, (INT16)pObject->Position.y, sZ, pObject->sGridNo, pObject->Obj.usItem, GET_OBJECT_LEVEL( pObject->Position.z - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pObject->sGridNo ].sHeight ) ), DIRECTION_IRRELEVANT, &pObject->Obj );
}
else if ( Item[ pObject->Obj.usItem ].usItemClass == IC_BOMB ) //if ( pObject->Obj.usItem == MORTAR_SHELL )
{
sZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z );
IgniteExplosion( pObject->ubOwner, (INT16)pObject->Position.x, (INT16)pObject->Position.y, sZ, pObject->sGridNo, pObject->Obj.usItem, GET_OBJECT_LEVEL( pObject->Position.z - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pObject->sGridNo ].sHeight ) ) );
}
}
// OJW - 20021002 - MP Explosives
if (is_networked && is_client && !pObject->mpIsFromRemoteClient)
{
// send results to other clients
send_grenade_result( (INT16)pObject->Position.x, (INT16)pObject->Position.y, sZ , pObject->sGridNo , pObject->ubOwner, pObject->iID, fIsDud);
}
}
BOOLEAN SavePhysicsTableToSaveGameFile( HWFILE hFile )
{
UINT32 uiNumBytesWritten=0;
UINT16 usCnt=0;
UINT32 usPhysicsCount=0;
for( usCnt=0; usCnt<NUM_OBJECT_SLOTS; usCnt++ )
{
//if the REAL_OBJECT is active, save it
if( ObjectSlots[ usCnt ].fAllocated )
{
usPhysicsCount++;
}
}
//Save the number of REAL_OBJECTs in the array
FileWrite( hFile, &usPhysicsCount, sizeof( UINT32 ), &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( UINT32 ) )
{
return( FALSE );
}
if( usPhysicsCount != 0 )
{
for( usCnt=0; usCnt<NUM_OBJECT_SLOTS; usCnt++ )
{
//if the REAL_OBJECT is active, save it
if( ObjectSlots[ usCnt ].fAllocated )
{
//Save the the REAL_OBJECT structure
if ( !ObjectSlots[usCnt].Save(hFile) )
{
return( FALSE );
}
}
}
}
return( TRUE );
}
BOOLEAN LoadPhysicsTableFromSavedGameFile( HWFILE hFile )
{
UINT32 uiNumBytesRead=0;
UINT16 usCnt=0;
//make sure the objects are not allocated
for (int x = 0; x < NUM_OBJECT_SLOTS; ++x)
{
ObjectSlots[x].initialize();
}
//Load the number of REAL_OBJECTs in the array
FileRead( hFile, &guiNumObjectSlots, sizeof( UINT32 ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( UINT32 ) )
{
return( FALSE );
}
//loop through and add the objects
for( usCnt=0; usCnt<guiNumObjectSlots; usCnt++ )
{
//Load the the REAL_OBJECT structure
if ( !ObjectSlots[usCnt].Load(hFile) )
{
return( FALSE );
}
ObjectSlots[usCnt].pNode = NULL;
ObjectSlots[usCnt].pShadow = NULL;
ObjectSlots[usCnt].iID = usCnt;
}
return( TRUE );
}
INT32 RandomGridFromRadius( INT32 sSweetGridNo, INT8 ubMinRadius, INT8 ubMaxRadius )
{
INT16 sX, sY;
INT32 sGridNo = NOWHERE;
INT32 leftmost;
BOOLEAN fFound = FALSE;
UINT32 cnt = 0;
if ( ubMaxRadius == 0 || ubMinRadius == 0 )
{
return( sSweetGridNo );
}
do
{
sX = (UINT16)PreRandom( ubMaxRadius );
sY = (UINT16)PreRandom( ubMaxRadius );
if ( ( sX < ubMinRadius || sY < ubMinRadius ) && ubMaxRadius != ubMinRadius )
{
continue;
}
if ( PreRandom( 2 ) == 0 )
{
sX = sX * -1;
}
if ( PreRandom( 2 ) == 0 )
{
sY = sY * -1;
}
leftmost = ( ( sSweetGridNo + ( WORLD_COLS * sY ) )/ WORLD_COLS ) * WORLD_COLS;
sGridNo = sSweetGridNo + ( WORLD_COLS * sY ) + sX;
if ( sGridNo == sSweetGridNo )
{
continue;
}
if ( sGridNo >=0 && sGridNo < WORLD_MAX &&
sGridNo >= leftmost && sGridNo < ( leftmost + WORLD_COLS ) )
{
fFound = TRUE;
}
cnt++;
if ( cnt > 50 )
{
return( NOWHERE );
}
} while( !fFound );
return( sGridNo );
}
UINT32 GetArtilleryTargetGridNo( UINT32 sTargetGridNo, INT8 bRadius )
{
return RandomGridFromRadius( sTargetGridNo, 1, bRadius );
}
BOOLEAN GetArtilleryLaunchParams( UINT32 sStartingGridNo, UINT32 sTargetGridNo, INT16 sStartZ, INT16 sEndZ, UINT16 usLauncher, OBJECTTYPE* pObj, FLOAT* pdForce, FLOAT* pdDegrees)
{
FLOAT dMagForce, dMaxForce, dMinForce;
FLOAT dDegrees = OUTDOORS_START_ANGLE;
INT16 sMinRange = MIN_MORTAR_RANGE;
sStartZ = 256;
// Find force for basic
INT32 sFinalGridNo = 0;
FindBestForceForTrajectory( sStartingGridNo, sTargetGridNo, sStartZ, sEndZ, dDegrees, pObj, &sFinalGridNo, &dMagForce );
INT32 uiMaxRange = GetModifiedGunRange(usLauncher) / CELL_X_SIZE;
dMaxForce = CalculateForceFromRange( NULL, (INT16) uiMaxRange, (FLOAT)( PI/4 ) );
if ( dMagForce > dMaxForce )
dMagForce = dMaxForce;
dMinForce = CalculateForceFromRange( NULL, (INT16)( sMinRange / 10 ), (FLOAT)( PI / 4 ) );
if ( dMagForce < dMinForce )
dMagForce = dMinForce;
(*pdForce) = dMagForce;
(*pdDegrees) = dDegrees;
return TRUE;
}