mirror of
https://github.com/1dot13/source.git
synced 2026-07-22 13:40:22 +02:00
Made direction variables more consistent as unsigned chars. Several function prototypes updated for this. Many memory leaks plugged: External options, video overlays, laptop file lists, tactical message queue, strategic pathing, autobandage, merc hiring, detailed placements, crate in Drassen, tactical placement New functions and attributes for soldiers in LUA (still for debugging at best): Soldier.APs (current APs), Soldier.changestance (changes stance, uses game heights) File catalog ignores .SVN directories (game load speedup) Fix CTD in mouse regions JA2 window now refuses to move to negative coords Checks to prevent DirectX-related infinite loops due to minimizing and task switching Invading enemies should now appear on the borders even when reinforcements disabled in .ini Suppression should no longer work on mercs in medium water Infant/Young creatures use restored spit instead of Molotov Creatures begin with their 'guns' (spit) 'locked and loaded' (cartridge in chamber) Further fix to AXP and AlaarDB's weapon ready check: Now 'firing' is always counted as 'ready'. Prevent mercs from falling and flying back through obstacles Check whether battle group is even set before testing its location for battle setup Burst spread locations now limited to 6, the limit within the soldier struct Extra burst spread locations zeroed out so that they aren't used unless necessary Spread code now only shoots at locations in the spread, and will shoot at all 6 Only mercs in the sector where autobandage happens will be made to stand up after it's done Throwing a grenade at the tail of the plane in Drassen should not result in a CTD Reset attack busy count when loading a new sector git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@1347 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2717 lines
75 KiB
C++
2717 lines
75 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"
|
|
|
|
#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( o ) ( ( o->Obj.usItem == MORTAR_SHELL ) ) // && ( o->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) )
|
|
#define OBJECT_DETONATE_ON_IMPACT( o ) ( ( Item[o->Obj.usItem].usItemClass == IC_BOMB ) ) // && ( o->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( INT16 sRange, FLOAT dDegrees );
|
|
|
|
UINT16 RandomGridFromRadius( INT16 sSweetGridNo, INT8 ubMinRadius, INT8 ubMaxRadius );
|
|
|
|
// Lesh: needed to fix item throwing through window
|
|
extern INT16 DirIncrementer[8];
|
|
|
|
void HandleArmedObjectImpact( REAL_OBJECT *pObject );
|
|
void ObjectHitWindow( INT16 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce );
|
|
FLOAT CalculateObjectTrajectory( INT16 sTargetZ, OBJECTTYPE *pItem, vector_3 *vPosition, vector_3 *vForce, INT16 *psFinalGridNo );
|
|
vector_3 FindBestForceForTrajectory( INT16 sSrcGridNo, INT16 sGridNo,INT16 sStartZ, INT16 sEndZ, real dzDegrees, OBJECTTYPE *pItem, INT16 *psGridNo, FLOAT *pzMagForce );
|
|
INT32 ChanceToGetThroughObjectTrajectory( INT16 sTargetZ, OBJECTTYPE *pItem, vector_3 *vPosition, vector_3 *vForce, INT16 *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 );
|
|
|
|
|
|
/// 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 ] );
|
|
|
|
memset( pObject, 0, sizeof( REAL_OBJECT ) );
|
|
|
|
// OK, GET OBJECT DATA AND COPY
|
|
memcpy( &(pObject->Obj), pGameObj, sizeof( OBJECTTYPE ) );
|
|
|
|
// 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 ( pObject->sGridNo != NOWHERE )
|
|
{
|
|
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");
|
|
real DeltaTime = 0;
|
|
real CurrentTime = 0;
|
|
real TargetTime = DeltaTime;
|
|
INT32 iCollisionID;
|
|
BOOLEAN fEndThisObject = FALSE;
|
|
|
|
if ( !PhysicsUpdateLife( pObject, (float)deltaT ) )
|
|
{
|
|
// DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: don't update life, returning");
|
|
return;
|
|
}
|
|
|
|
// DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: check life");
|
|
if ( pObject->fAlive )
|
|
{
|
|
CurrentTime = 0;
|
|
TargetTime = (float)deltaT;
|
|
|
|
//DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: do subtime");
|
|
// Do subtime here....
|
|
DeltaTime = (float)deltaT / (float)10;
|
|
|
|
if ( !PhysicsComputeForces( pObject ) )
|
|
{
|
|
// DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"SimulateObject: couldn't compute forces");
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
}
|
|
|
|
// End life if we are out of bounds....
|
|
if ( !GridNoOnVisibleWorldTile( pObject->sGridNo ) )
|
|
{
|
|
pObject->fAlive = FALSE;
|
|
}
|
|
|
|
if ( !pObject->fAlive )
|
|
{
|
|
if ( !pObject->fTestObject )
|
|
{
|
|
int i = 0;
|
|
}
|
|
|
|
// 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->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;
|
|
EVENT_InitNewSoldierAnim( pSoldier, END_CATCH, 0 , FALSE );
|
|
break;
|
|
|
|
case ANIM_CROUCH:
|
|
|
|
pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
|
|
EVENT_InitNewSoldierAnim( pSoldier, 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;
|
|
INT16 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;
|
|
INT16 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 = (INT16)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;
|
|
INT16 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( INT16 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( INT16 sSrcGridNo, INT16 sGridNo,INT16 sStartZ, INT16 sEndZ, real dzDegrees, OBJECTTYPE *pItem, INT16 *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 * ( sqrt( ( GRAVITY * dRange ) / sin( 2 * 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 );
|
|
|
|
// OK, we have our force, calculate change to get through without collide
|
|
//if ( ChanceToGetThroughObjectTrajectory( sEndZ, pItem, &vPosition, &vForce, NULL ) == 0 )
|
|
{
|
|
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Chance to get through throw is 0." );
|
|
}
|
|
|
|
if ( pdMagForce )
|
|
{
|
|
(*pdMagForce) = dForce;
|
|
}
|
|
|
|
#ifdef JA2TESTVERSION
|
|
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Number of integration: %d", iNumChecks );
|
|
#endif
|
|
|
|
return( vForce );
|
|
}
|
|
|
|
|
|
INT16 FindFinalGridNoGivenDirectionGridNoForceAngle( INT16 sSrcGridNo, INT16 sGridNo, INT16 sStartZ, INT16 sEndZ, real dForce, real dzDegrees, OBJECTTYPE *pItem )
|
|
{
|
|
vector_3 vDirNormal, vPosition, vForce;
|
|
INT16 sDestX, sDestY, sSrcX, sSrcY;
|
|
real dRange;
|
|
INT16 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( INT16 sSrcGridNo, INT16 sGridNo,INT16 sStartZ, INT16 sEndZ, real dForce, OBJECTTYPE *pItem, INT16 *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( INT16 sSrcGridNo, INT16 sGridNo, INT16 sStartZ, INT16 sEndZ, real dForce, real dzDegrees, OBJECTTYPE *pItem, INT16 *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, INT16 *psFinalGridNo )
|
|
{
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateObjectTrajectory");
|
|
INT32 iID;
|
|
REAL_OBJECT *pObject;
|
|
FLOAT dDiffX, dDiffY;
|
|
INT16 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, INT16 *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, INT16 sGridNo, UINT8 ubHeight, real dForce, OBJECTTYPE *pItem, INT16 *psGridNo )
|
|
{
|
|
real dAngle;
|
|
INT16 sSrcX, sSrcY;
|
|
|
|
ConvertGridNoToCenterCellXY( pSoldier->sGridNo, &sSrcX, &sSrcY );
|
|
|
|
dAngle = FindBestAngleForTrajectory( pSoldier->sGridNo, sGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel ), ubHeight, dForce, pItem, psGridNo );
|
|
|
|
// new we have defaut angle value...
|
|
return( dAngle );
|
|
}
|
|
|
|
|
|
|
|
void CalculateLaunchItemBasicParams( SOLDIERTYPE *pSoldier, OBJECTTYPE *pItem, INT16 sGridNo, UINT8 ubLevel, INT16 sEndZ, FLOAT *pdMagForce, FLOAT *pdDegrees, INT16 *psFinalGridNo, BOOLEAN fArmed )
|
|
{
|
|
INT16 sInterGridNo;
|
|
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->bLevel );
|
|
|
|
// Are we armed, and are we throwing a LAUNCHABLE?
|
|
|
|
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->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->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 ( sInterGridNo != NOWHERE )
|
|
{
|
|
// 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( (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->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->bLevel ), 150, dMagForce, pItem, psFinalGridNo );
|
|
|
|
if ( dNewDegrees != 0 )
|
|
{
|
|
dDegrees = dNewDegrees;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( fThroughIntermediateGridNo )
|
|
{
|
|
dDegrees = FindBestAngleForTrajectory( pSoldier->sGridNo, sInterGridNo, GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel ), 150, dMagForce, pItem, psFinalGridNo );
|
|
}
|
|
}
|
|
|
|
|
|
(*pdMagForce) = dMagForce;
|
|
(*pdDegrees ) = dDegrees;
|
|
}
|
|
|
|
|
|
BOOLEAN CalculateLaunchItemChanceToGetThrough( SOLDIERTYPE *pSoldier, OBJECTTYPE *pItem, INT16 sGridNo, UINT8 ubLevel, INT16 sEndZ, INT16 *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->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( INT16 sRange, FLOAT dDegrees )
|
|
{
|
|
FLOAT dMagForce;
|
|
INT16 sSrcGridNo, sDestGridNo;
|
|
OBJECTTYPE Object;
|
|
INT16 sFinalGridNo;
|
|
|
|
// OK, use a fake gridno, find the new gridno based on range, use height of merc, end height of ground,
|
|
// 45 degrees
|
|
sSrcGridNo = 4408;
|
|
sDestGridNo = 4408 + ( sRange * WORLD_COLS );
|
|
|
|
// Use a grenade objecttype
|
|
CreateItem( HAND_GRENADE, 100, &Object );
|
|
|
|
FindBestForceForTrajectory( sSrcGridNo, sDestGridNo, GET_SOLDIER_THROW_HEIGHT( 0 ), 0, dDegrees, &Object, &sFinalGridNo, &dMagForce );
|
|
|
|
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 );
|
|
|
|
dMagForce = CalculateForceFromRange( (INT16) uiMaxRange, dDegrees );
|
|
|
|
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CalculateSoldierMaxForce: done");
|
|
return( dMagForce );
|
|
}
|
|
|
|
|
|
#define MAX_MISS_BY 30
|
|
#define MIN_MISS_BY 1
|
|
#define MAX_MISS_RADIUS 5
|
|
|
|
void CalculateLaunchItemParamsForThrow( SOLDIERTYPE *pSoldier, INT16 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;
|
|
INT16 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;
|
|
}
|
|
|
|
//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 ) );
|
|
|
|
pSoldier->pTempObject = (OBJECTTYPE *) MemAlloc( sizeof( OBJECTTYPE ) );
|
|
|
|
memcpy( pSoldier->pTempObject, pItem, sizeof( OBJECTTYPE ) );
|
|
pSoldier->pThrowParams->dX = (float)sSrcX;
|
|
pSoldier->pThrowParams->dY = (float)sSrcY;
|
|
|
|
|
|
sStartZ = GET_SOLDIER_THROW_HEIGHT( pSoldier->bLevel );
|
|
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->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 )
|
|
{
|
|
pSoldier = MercPtrs[ usStructureID ];
|
|
|
|
sDamage = 1;
|
|
sBreath = 0;
|
|
|
|
EVENT_SoldierGotHit( pSoldier, 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 )
|
|
{
|
|
EVENT_InitNewSoldierAnim( pSoldier, CATCH_STANDING, 0 , FALSE );
|
|
}
|
|
else if ( gAnimControl[ pSoldier->usAnimState ].ubHeight == ANIM_CROUCH )
|
|
{
|
|
EVENT_InitNewSoldierAnim( pSoldier, 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 ) / 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;
|
|
EVENT_InitNewSoldierAnim( pSoldier, END_CATCH, 0 , FALSE );
|
|
break;
|
|
|
|
case ANIM_CROUCH:
|
|
|
|
pSoldier->usPendingAnimation = NO_PENDING_ANIMATION;
|
|
EVENT_InitNewSoldierAnim( pSoldier, 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->name, ShortItemNames[ usItem ] );
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
//#define TESTDUDEXPLOSIVES
|
|
|
|
void HandleArmedObjectImpact( REAL_OBJECT *pObject )
|
|
{
|
|
INT16 sZ;
|
|
BOOLEAN fDoImpact = FALSE;
|
|
BOOLEAN fCheckForDuds = FALSE;
|
|
OBJECTTYPE *pObj;
|
|
INT32 iTrapped = 0;
|
|
UINT16 usFlags = 0;
|
|
INT8 bLevel = 0;
|
|
|
|
// 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 )
|
|
{
|
|
// 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->ItemData.Generic.bStatus[0] >= USABLE && ( PreRandom( 100 ) < (UINT32) pObj->ItemData.Generic.bStatus[0] + PreRandom( 50 ) ) ) )
|
|
#endif
|
|
{
|
|
fDoImpact = TRUE;
|
|
}
|
|
else // didn't go off!
|
|
{
|
|
#ifdef TESTDUDEXPLOSIVES
|
|
if ( 1 )
|
|
#else
|
|
if ( pObj->ItemData.Generic.bStatus[0] >= USABLE && PreRandom(100) < (UINT32) pObj->ItemData.Generic.bStatus[0] + PreRandom( 50 ) )
|
|
#endif
|
|
{
|
|
iTrapped = PreRandom( 4 ) + 2;
|
|
}
|
|
|
|
if ( iTrapped )
|
|
{
|
|
// Start timed bomb...
|
|
usFlags |= WORLD_ITEM_ARMED_BOMB;
|
|
|
|
pObj->ItemData.Trigger.bDetonatorType = BOMB_TIMED;
|
|
pObj->ItemData.Trigger.BombTrigger.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 )
|
|
{
|
|
DoMercBattleSound( MercPtrs[ pObject->ubOwner ], (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 ) ) );
|
|
}
|
|
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 ) ) );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
FileWrite( hFile, &ObjectSlots[usCnt], sizeof( REAL_OBJECT ), &uiNumBytesWritten );
|
|
if( uiNumBytesWritten != sizeof( REAL_OBJECT ) )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
BOOLEAN LoadPhysicsTableFromSavedGameFile( HWFILE hFile )
|
|
{
|
|
UINT32 uiNumBytesRead=0;
|
|
UINT16 usCnt=0;
|
|
|
|
//make sure the objects are not allocated
|
|
memset( ObjectSlots, 0, NUM_OBJECT_SLOTS * sizeof( REAL_OBJECT ) );
|
|
|
|
//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
|
|
FileRead( hFile, &ObjectSlots[usCnt], sizeof( REAL_OBJECT ), &uiNumBytesRead );
|
|
if( uiNumBytesRead != sizeof( REAL_OBJECT ) )
|
|
{
|
|
return( FALSE );
|
|
}
|
|
|
|
ObjectSlots[usCnt].pNode = NULL;
|
|
ObjectSlots[usCnt].pShadow = NULL;
|
|
ObjectSlots[usCnt].iID = usCnt;
|
|
}
|
|
|
|
return( TRUE );
|
|
}
|
|
|
|
|
|
UINT16 RandomGridFromRadius( INT16 sSweetGridNo, INT8 ubMinRadius, INT8 ubMaxRadius )
|
|
{
|
|
INT16 sX, sY;
|
|
INT16 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 );
|
|
}
|