#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" #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 ); 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 ) { 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->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") ); 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") ); ReduceAttackBusyCount( pObject->ubOwner, FALSE ); // 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; FLOAT dNormalX, dNormalY, dNormalZ; INT16 sGridNo; // 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 ) { 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; 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 ); 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 ) { 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 ); 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_START_ANGLE; dDegrees = GLAUNCHER_HIGHER_LEVEL_START_ANGLE; } else { dDegrees = GLAUNCHER_START_ANGLE; } fGLauncher = TRUE; sMinRange = MIN_MORTAR_RANGE; //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 ); } 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 ) != NO_SOLDIER ) { 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"); // 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->bDirection, 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->bStatus[0] >= USABLE && ( PreRandom( 100 ) < (UINT32) pObj->bStatus[0] + PreRandom( 50 ) ) ) ) #endif { fDoImpact = TRUE; } else // didn't go off! { #ifdef TESTDUDEXPLOSIVES if ( 1 ) #else if ( pObj->bStatus[0] >= USABLE && PreRandom(100) < (UINT32) pObj->bStatus[0] + PreRandom( 50 ) ) #endif { iTrapped = PreRandom( 4 ) + 2; } if ( iTrapped ) { // Start timed bomb... usFlags |= WORLD_ITEM_ARMED_BOMB; pObj->bDetonatorType = BOMB_TIMED; pObj->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 ) { DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("physics.cpp line 2403")); if ( Item[pObject->Obj.usItem].flare ) { //if the light object will ber created OFF the ground if( pObject->Position.z > 0 ) { //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, LIGHT_FLARE_MARK_1 ); } } 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=0 && sGridNo < WORLD_MAX && sGridNo >= leftmost && sGridNo < ( leftmost + WORLD_COLS ) ) { fFound = TRUE; } cnt++; if ( cnt > 50 ) { return( NOWHERE ); } } while( !fFound ); return( sGridNo ); }