#include "connect.h" #ifdef PRECOMPILEDHEADERS #include "Tactical All.h" #else #include "builddefines.h" #include #include #include #include "wcheck.h" #include "Isometric Utils.h" #include "debug.h" #include "los.h" #include "animation control.h" #include "Random.h" //#include "soldier control.h" #include "Event Pump.h" #include "overhead.h" #include "weapons.h" #include "opplist.h" #include "bullets.h" #include "phys math.h" #include "items.h" #include "Soldier Profile.h" #include "worldman.h" #include "rotting corpses.h" #include "GameSettings.h" #include "keys.h" #include "message.h" #include "Structure Wrap.h" #include "campaign.h" #include "environment.h" #include "Pathai.h" #include "Soldier macros.h" #include "strategicmap.h" #include "Interface.h" #include "points.h" #include "Smell.h" #include "Text.h" #include "Quests.h" #include "items.h" #endif #include "fresh_header.h" #include "test_space.h" //forward declarations of common classes to eliminate includes class OBJECTTYPE; class SOLDIERTYPE; #define STEPS_FOR_BULLET_MOVE_TRAILS 10 #define STEPS_FOR_BULLET_MOVE_SMALL_TRAILS 5 #define STEPS_FOR_BULLET_MOVE_FIRE_TRAILS 5 #define ALWAYS_CONSIDER_HIT (STRUCTURE_WALLSTUFF | STRUCTURE_CAVEWALL | STRUCTURE_FENCE) extern UINT32 guiSoldierFlags; extern INT16 DirIncrementer[8]; extern BOOLEAN fTracer; static FIXEDPT gqStandardWallHeight = INT32_TO_FIXEDPT( WALL_HEIGHT_UNITS ); static FIXEDPT gqStandardWindowBottomHeight = INT32_TO_FIXEDPT( WINDOW_BOTTOM_HEIGHT_UNITS ); static FIXEDPT gqStandardWindowTopHeight = INT32_TO_FIXEDPT( WINDOW_TOP_HEIGHT_UNITS ); #define FIXEDPT_MULTIPLY( a, b ) ( (a / 256) * (b / 256) ) //ADB When a merc calcs CTGT for a thrown item he uses a GLOCK temp item //but we don't want to recreate it every single time CTGT is called, so init the GLOCK in InitializeJA2() OBJECTTYPE GLOCK_17_ForUseWithLOS; UINT32 FPMult32(UINT32 uiA, UINT32 uiB) { UINT32 uiResult; __asm { // Load the 32-bit registers with the two values mov eax, uiA mov ebx, uiB // Multiply them // Top 32 bits (whole portion) goes into edx // Bottom 32 bits (fractional portion) goes into eax imul ebx // Shift the fractional portion back to (lower) 16 bits shr eax, 16 // Shift the whole portion to 16 bits, in the upper word shl edx, 16 // At this point, we have edx xxxx0000 and eax 0000xxxx // Combine the two words into a dword or eax, edx // Put the result into a returnable variable mov uiResult, eax } return(uiResult); } //zilpin: pellet spread patterns externalized in XML t_SpreadPattern *gpSpreadPattern=NULL; INT32 giSpreadPatternCount=0; char * gSpreadPatternMethodNames[] = {"RECTANGLE","DIAMOND","ELLIPSE",NULL}; int giSpreadPatternMethod_Default = SPREADPATTERNMETHOD_ELLIPSE; //zilpin: ddShotgunSpread is only used as a backup if the XML file does not exist. static DOUBLE ddShotgunSpread[3][BUCKSHOT_SHOTS][2] = { { // spread of about 2 degrees in all directions // Horiz, Vert { 0.0, 0.0 }, { -0.012, 0.0 }, { +0.012, 0.0 }, { 0.0, -0.012}, { 0.0, +0.012}, { -0.008, -0.008}, { -0.008, +0.008}, { +0.008, -0.008}, { +0.008, +0.008} }, { // duckbill flattens the spread and makes it wider horizontally (5 degrees) // Horiz, Vert { 0.0, 0.0 }, { -0.008, 0.0 }, { +0.008, 0.0 }, { -0.016, 0.0 }, { +0.016, 0.0 }, { -0.024, 0.0 }, { +0.024, 0.0 }, { -0.032, 0.0 }, { +0.032, 0.0 }, }, { // flamethrower more spread out // Horiz, Vert { 0.0, 0.0 }, { -0.120, 0.0 }, { +0.120, 0.0 }, { 0.0, -0.120}, { 0.0, +0.120}, { -0.080, -0.080}, { -0.080, +0.080}, { +0.080, -0.080}, { +0.080, +0.080} }, }; //zilpin: pellet spread patterns externalized in XML //Decide what spread pattern to use for an object in inventory. //Attachment trumps Weapon trumps Ammo trumps AmmoType //If multiple attachments, then the first one encountered is used. //This will work for non-weapons, but ammo and ammo type are only checked for guns. INT32 GetSpreadPattern( OBJECTTYPE * pObj ) { int i=0,n=0; if( !pObj || !pObj->usItem || !pObj->exists() ) return 0; //If there are attachments, check them. Stop on the first one with something defined. //Dear God, I hate C++ iterators. What a fugly mess. for (attachmentList::iterator iter = pObj[0][0]->attachments.begin(); iter != pObj[0][0]->attachments.end(); ++iter){ if( n=Item[ iter->usItem ].spreadPattern ) //An attachment has it, and it trumps everything, so return it's value. return n; } //The object's own spread pattern, if it has one. if( n=Item[pObj->usItem].spreadPattern ) return n; //If a gun, and don't have a pattern yet, check it's ammo. if( IC_GUN & Item[pObj->usItem].usItemClass ){ //Check for the ammo first. if( n=Item[ pObj[0][0]->data.gun.usGunAmmoItem ].spreadPattern ) return n; //If no pattern for the ammo, try the ammo type. if( n=AmmoTypes[pObj[0][0]->data.gun.ubGunAmmoType].spreadPattern ) return n; } /* This is the old code to iterate through all the attachments. No longer works, since the NewInv project object-orientified everything. * //If there are attachments, check them. Stop on the first one with something defined. for (i=0; iusAttachItem[i]) if( n=Item[ pObj->usAttachItem[i] ].spreadPattern ) //The attachment has it, and it trumps everything, so return it's value. return n; //The object's own spread pattern, if it has one. if( n=Item[pObj->usItem].spreadPattern ) return n; //If a gun, and don't have a pattern yet, check it's ammo. if( IC_GUN & Item[pObj->usItem].usItemClass ){ //Check for the ammo first. if( n=Item[ pObj->ItemData.Gun.usGunAmmoItem ].spreadPattern ) return n; //If no pattern for the ammo, try the ammo type. if( n=AmmoTypes[pObj->ItemData.Gun.ubGunAmmoType].spreadPattern ) return n; } */ //No pattern for anything found, so use the default. return 0; } static UINT8 gubTreeSightReduction[ANIM_STAND + 1] = { 0, 8, // prone 0, 7, // crouched 0, 0, 6 // standing }; #define MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE 25 #define MAX_CHANCE_OF_HITTING_STRUCTURE 90 static UINT32 guiStructureHitChance[ MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE + 1] = { 0, // 0 tiles 0, 0, 2, 4, 7, // 5 tiles 10, 14, 18, 23, 28, // 10 tiles 34, 40, 47, 54, 60, // 15 tiles 66, 71, 74, 76, 78,// 20 tiles 80, 82, 84, 86, 88,// 25 tiles }; #define PERCENT_BULLET_SLOWED_BY_RANGE 25 #define MIN_DIST_FOR_HIT_FRIENDS 30 #define MIN_DIST_FOR_HIT_FRIENDS_UNAIMED 15 #define MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE 3 #define RADIANS_IN_CIRCLE 6.283 #define DEGREES_22_5 (RADIANS_IN_CIRCLE * 22.5 / 360) #define DEGREES_45 (RADIANS_IN_CIRCLE * 45 / 360) // note: these values are in RADIANS!! // equal to 15 degrees #define MAX_AIMING_SCREWUP (RADIANS_IN_CIRCLE * 15 / 360) // min aiming screwup is X degrees, gets divided by distance in tiles #define MIN_AIMING_SCREWUP (RADIANS_IN_CIRCLE * 22 / 360) //#define MAX_AIMING_SCREWUP 0.2618 // equal to 10 degrees //#define MAX_AIMING_SCREWUP_VERTIC 0.1745 #define SMELL_REDUCTION_FOR_NEARBY_OBSTACLE 20 //80 #define STANDING_CUBES 3 // MoveBullet and ChanceToGetThrough use this array to maintain which // of which structures in a tile might be hit by a bullet. #define MAX_LOCAL_STRUCTURES 100 STRUCTURE * gpLocalStructure[MAX_LOCAL_STRUCTURES]; UINT32 guiLocalStructureCTH[MAX_LOCAL_STRUCTURES]; UINT8 gubLocalStructureNumTimesHit[MAX_LOCAL_STRUCTURES]; BOOLEAN CalculateLOSNormal( STRUCTURE *pStructure, INT8 bLOSX, INT8 bLOSY, INT8 bLOSZ, FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ, FLOAT *pdNormalX, FLOAT *pdNormalY, FLOAT *pdNormalZ ); extern UINT8 gubMaterialArmour[]; #ifdef LOS_DEBUG LOSResults gLOSTestResults = {0}; #endif FIXEDPT FloatToFixed( FLOAT dN ) { FIXEDPT qN; // verify that dN is within the range storable by FIXEDPT? // first get the whole part qN = (INT32) (dN * FIXEDPT_FRACTIONAL_RESOLUTION); //qN = INT32_TO_FIXEDPT( (INT32)dN ); // now add the fractional part //qN += (INT32)(((dN - (INT32) dN)) * FIXEDPT_FRACTIONAL_RESOLUTION); return( qN ); } FLOAT FixedToFloat( FIXEDPT qN ) { return( ((FLOAT) qN) / FIXEDPT_FRACTIONAL_RESOLUTION ); } // // fixed-point arithmetic stuff ends here // FLOAT Distance3D( FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ ) { return( (FLOAT) sqrt( (DOUBLE) (dDeltaX * dDeltaX + dDeltaY * dDeltaY + dDeltaZ * dDeltaZ) )); } FLOAT Distance2D( FLOAT dDeltaX, FLOAT dDeltaY ) { return( (FLOAT) sqrt( (DOUBLE) (dDeltaX * dDeltaX + dDeltaY * dDeltaY ))); } //#define DEBUGLOS #if defined( JA2BETAVERSION ) && defined( DEBUGLOS ) void DebugLOS( STR szOutput ) { DebugMsg(TOPIC_JA2,DBG_LEVEL_3,szOutput); FILE * DebugFile; if ((DebugFile = fopen( "losdebug.txt", "a+t" )) != NULL) { fputs( szOutput, DebugFile ); fputs( "\n", DebugFile ); fclose( DebugFile ); } } #else #define DebugLOS( a ) #endif enum { LOC_OTHER, LOC_0_4, LOC_3_4, LOC_4_0, LOC_4_3, LOC_4_4 } LocationCode; BOOLEAN ResolveHitOnWall( STRUCTURE * pStructure, INT32 iGridNo, INT8 bLOSIndexX, INT8 bLOSIndexY, DOUBLE ddHorizAngle ) { BOOLEAN fNorthSouth, fEastWest; BOOLEAN fTopLeft, fTopRight; INT8 bLocation = LOC_OTHER; switch ( bLOSIndexX ) { case 0: if ( bLOSIndexY == 4 ) { bLocation = LOC_0_4; } break; case 3: if ( bLOSIndexY == 4 ) { bLocation = LOC_3_4; } break; case 4: switch( bLOSIndexY ) { case 0: bLocation = LOC_4_0; break; case 3: bLocation = LOC_4_3; break; case 4: bLocation = LOC_4_4; break; default: break; } break; default: break; } if ( bLocation == LOC_OTHER ) { // these spots always block return( TRUE ); } // use cartesian angles for god's sakes -CJC ddHorizAngle = -ddHorizAngle; fNorthSouth = ( ( ddHorizAngle < (0) && ddHorizAngle > ( -PI * 1 / 2) ) || ( ddHorizAngle > ( PI * 1 / 2 ) && ddHorizAngle < (PI) ) ); fEastWest = ( ( ddHorizAngle > (0) && ddHorizAngle < (PI * 1 / 2) ) || ( ddHorizAngle < (-PI * 1 / 2) && ddHorizAngle > ( -PI ) ) ); fTopLeft = ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT ); fTopRight = ( pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT ); if ( fNorthSouth ) { // Check N-S at west corner: 4,4 4,3 0,4 if ( bLocation == LOC_4_3 || bLocation == LOC_4_4 ) { // if wall orientation is top-right, then check S of this location // if wall orientation is top-left, then check E of this location // if no wall of same orientation there, let bullet through if ( fTopRight ) { if (!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( SOUTH )) ) && !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo) ) && !OpenRightOrientedDoorWithDoorOnRightOfEdgeExists( (INT16) (iGridNo + DirectionInc( SOUTH )) ) ) { return( FALSE ); } } } else if ( bLocation == LOC_0_4 ) { if ( fTopLeft ) { if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( SOUTHWEST ) ) ) && !OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( WEST )) ) ) { return( FALSE ); } } } // Check N-S at east corner: 4,4 3,4 4,0 if ( bLocation == LOC_4_4 || bLocation == LOC_3_4 ) { if ( fTopLeft ) { if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( EAST )) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo) ) && !OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( EAST )) ) ) { return( FALSE ); } } } else if ( bLocation == LOC_4_0 ) { // if door is normal and OPEN and outside type then we let N-S pass if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) ) { if ( pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT ) { return( FALSE ); } } else if ( fTopRight ) { if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( NORTHEAST )) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( NORTH ) ) ) && !OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( NORTHEAST )) ) ) { return( FALSE ); } } } } if ( fEastWest ) { // Check E-W at north corner: 4,4 4,0 0,4 if ( bLocation == LOC_4_4 ) { if ( pStructure->ubWallOrientation == NO_ORIENTATION) { // very top north corner of building, and going (screenwise) west or east return( FALSE ); } } else if ( bLocation == LOC_4_0 ) { // maybe looking E-W at (screenwise) north corner of building // if wall orientation is top-right, then check N of this location // if no wall of same orientation there, let bullet through if ( fTopRight ) { if (!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( NORTH )) ) && !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( NORTH )) ) ) { return( FALSE ); } } } else if ( bLocation == LOC_0_4 ) { // if normal door and OPEN and inside type then we let E-W pass if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) ) { if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == INSIDE_TOP_RIGHT ) { return( FALSE ); } } // if wall orientation is top-left, then check W of this location // if no wall of same orientation there, let bullet through if ( fTopLeft ) { if (!WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) ) { return( FALSE ); } } } // Check E-W at south corner: 4,4 3,4 4,3 if ( bLocation == LOC_3_4 || bLocation == LOC_4_4 || bLocation == LOC_4_3 ) { if ( (bLocation == LOC_3_4 && fTopLeft) || (bLocation == LOC_4_3 && fTopRight) || (bLocation == LOC_4_4) ) { if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( EAST ) ) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( SOUTH ) ) ) && !OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( EAST ) ) ) && !OpenRightOrientedDoorWithDoorOnRightOfEdgeExists( (INT16) (iGridNo + DirectionInc( SOUTH ) ) ) ) { return( FALSE ); } } } } /* */ // currently handled: // E-W at north corner: (4,4), (0,4), (4,0) // N-S at east corner: (4,4) // N-S at west corner: (4,4) // could add: // N-S at east corner: (3, 4), (4, 0) // N-S at west corner: (0, 4), (4, 3) // E-W at south corner: (4, 4), (3, 4), (4, 3) (completely new) /* // possibly shooting at corner in which case we should let it pass if ( bLOSIndexX == 0) { if ( bLOSIndexY == (PROFILE_Y_SIZE - 1)) { // maybe looking E-W at (screenwise) north corner of building, or through open door if ( ( ddHorizAngle > (0) && ddHorizAngle < (PI * 1 / 2) ) || ( ddHorizAngle < (-PI * 1 / 2) && ddHorizAngle > ( -PI ) ) ) { // if door is normal and OPEN and inside type then we let E-W pass if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) ) { if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == INSIDE_TOP_RIGHT ) { fResolveHit = FALSE; } } // if wall orientation is top-left, then check W of this location // if no wall of same orientation there, let bullet through if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT ) { if (!WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) ) { fResolveHit = FALSE; } } } else if ( ( ddHorizAngle < (0) && ddHorizAngle > ( -PI * 1 / 2) ) || ( ddHorizAngle > ( PI * 1 / 2 ) && ddHorizAngle < (PI) ) ) { // maybe looking N-S at (screenwise) west corner of building // if wall orientation is top-left, then check W of this location // if no wall of same orientation there, let bullet through if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT ) { if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( WEST )) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( SOUTHWEST ) ) ) && !OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( WEST )) ) ) { fResolveHit = FALSE; } } } } } else if (bLOSIndexX == (PROFILE_X_SIZE - 1)) { if (bLOSIndexY == 0) { // maybe looking E-W at (screenwise) north corner of building if ( ( ddHorizAngle > (0) && ddHorizAngle < (PI * 1 / 2) ) || ( ddHorizAngle < (-PI * 1 / 2) && ddHorizAngle > ( -PI ) ) ) { // if wall orientation is top-right, then check N of this location // if no wall of same orientation there, let bullet through if ( pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT ) { if (!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( NORTH )) ) && !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( NORTH )) ) ) { fResolveHit = FALSE; } } } else { // if door is normal and OPEN and outside type then we let N-S pass if ( (pStructure->fFlags & STRUCTURE_DOOR) && (pStructure->fFlags & STRUCTURE_OPEN) ) { if ( pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT ) { fResolveHit = FALSE; } } } } else if ( bLOSIndexY == (PROFILE_Y_SIZE - 1) || bLOSIndexY == (PROFILE_Y_SIZE - 2) ) { // maybe (SCREENWISE) west or east corner of building and looking N if ( ( ddHorizAngle < (0) && ddHorizAngle > ( -PI * 1 / 2) ) || ( ddHorizAngle > ( PI * 1 / 2 ) && ddHorizAngle < (PI) ) ) { // if wall orientation is top-right, then check S of this location // if wall orientation is top-left, then check E of this location // if no wall of same orientation there, let bullet through if ( pStructure->ubWallOrientation == INSIDE_TOP_LEFT || pStructure->ubWallOrientation == OUTSIDE_TOP_LEFT ) { if ( !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo + DirectionInc( EAST )) ) && !WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo) ) && !OpenLeftOrientedDoorWithDoorOnLeftOfEdgeExists( (INT16) (iGridNo + DirectionInc( EAST )) ) ) { fResolveHit = FALSE; } } else if ( pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT ) { if (!WallOrClosedDoorExistsOfTopRightOrientation( (INT16) (iGridNo + DirectionInc( SOUTH )) ) && !WallOrClosedDoorExistsOfTopLeftOrientation( (INT16) (iGridNo) ) && !OpenRightOrientedDoorWithDoorOnRightOfEdgeExists( (INT16) (iGridNo + DirectionInc( SOUTH )) ) ) { fResolveHit = FALSE; } } } // the following only at 4,4 else if ( bLOSIndexY == (PROFILE_Y_SIZE - 1) ) { if ( pStructure->ubWallOrientation == NO_ORIENTATION) { // very top north corner of building, and going (screenwise) west or east fResolveHit = FALSE; } } } } */ return( TRUE ); } /* * * The line of sight code is now used to simulate smelling through the air (for monsters); * It obeys the following rules: * - ignores trees and vegetation * - ignores people * - should always start off with head height for both source and target, so that lying down makes no difference * - stop at closed windows * - stop for other obstacles * * Just for reference, normal sight obeys the following rules: * - trees & vegetation reduce the maximum sighting distance * - ignores people * - starts at height relative to stance * - ignores windows * - stops at other obstacles * */ INT32 LineOfSightTest( FLOAT dStartX, FLOAT dStartY, FLOAT dStartZ, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, int iTileSightLimit, UINT8 ubTreeSightReduction, INT8 bAware, INT32 bCamouflage, BOOLEAN fSmell, INT16 * psWindowGridNo, bool adjustForSight = true ) { // Parameters... // the X,Y,Z triplets should be obvious // TileSightLimit is the max # of tiles of distance visible // TreeSightReduction is the reduction in 10ths of tiles in max visibility for each LOS cube (5th of a tile) of // vegetation hit // Aware is whether the looker is aware of the target // Smell is whether this is a sight or a smell test // Now returns not a boolean but the adjusted (by cover) distance to the target, or 0 for unseen FIXEDPT qCurrX; FIXEDPT qCurrY; FIXEDPT qCurrZ; INT32 iGridNo; INT32 iCurrTileX; INT32 iCurrTileY; INT8 bLOSIndexX; INT8 bLOSIndexY; INT8 bOldLOSIndexX; INT8 bOldLOSIndexY; INT32 iOldCubesZ; INT32 iCurrCubesZ; FIXEDPT qLandHeight; INT32 iCurrAboveLevelZ; INT32 iCurrCubesAboveLevelZ; INT32 iStartCubesAboveLevelZ; INT32 iEndCubesAboveLevelZ; INT32 iStartCubesZ; INT32 iEndCubesZ; INT16 sDesiredLevel; INT32 iOldTileX; INT32 iOldTileY; FLOAT dDeltaX; FLOAT dDeltaY; FLOAT dDeltaZ; FIXEDPT qIncrX; FIXEDPT qIncrY; FIXEDPT qIncrZ; FLOAT dDistance; INT32 iDistance; INT32 iLoop; MAP_ELEMENT * pMapElement; STRUCTURE * pStructure; STRUCTURE * pRoofStructure = NULL; BOOLEAN fCheckForRoof; FIXEDPT qLastZ; FIXEDPT qDistToTravelX; FIXEDPT qDistToTravelY; INT32 iStepsToTravelX; INT32 iStepsToTravelY; INT32 iStepsToTravel; BOOLEAN fResolveHit; DOUBLE ddHorizAngle; INT32 iStructureHeight; FIXEDPT qWallHeight; BOOLEAN fOpaque; INT8 bSmoke = 0; if ( gTacticalStatus.uiFlags & DISALLOW_SIGHT ) { return( 0 ); } //ADB see notes at bottom as to why there is this 255 here INT32 iSightLimit = iTileSightLimit * CELL_X_SIZE; if (iTileSightLimit >= 255) { iSightLimit = (iTileSightLimit - 255) * CELL_X_SIZE; } //for the CTGT calculation, clamp it to the average distance INT32 iAdjSightLimit = iSightLimit; if (iSightLimit == 0) { // blind! return( 0 ); } DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("LineOfSightTest: Thermal Optics = %d",fSmell)); if (!bAware && !fSmell) { // trees are x3 as good at reducing sight if looker is unaware // and increase that up to double for camouflage! ubTreeSightReduction = (ubTreeSightReduction * 3) * (100 + bCamouflage) / 100; } // verify start and end to make sure we'll always be inside the map // hack end location to the centre of the tile, because there was a problem // seeing a presumably off-centre merc... dStartX = (FLOAT) (((INT32)dStartX) / 10) * 10 + 5; dStartY = (FLOAT) (((INT32)dStartY) / 10) * 10 + 5; dEndX = (FLOAT) (((INT32)dEndX) / 10) * 10 + 5; dEndY = (FLOAT) (((INT32)dEndY) / 10) * 10 + 5; dDeltaX = dEndX - dStartX; dDeltaY = dEndY - dStartY; dDeltaZ = dEndZ - dStartZ; dDistance = Distance3D( dDeltaX, dDeltaY, CONVERT_HEIGHTUNITS_TO_DISTANCE( dDeltaZ )); iDistance = (INT32) dDistance; if ( iDistance == 0 ) { return( FALSE ); } if ( dDistance != (FLOAT) iDistance ) { // add 1 step to account for fraction iDistance += 1; } if ( iDistance > iTileSightLimit * CELL_X_SIZE) { // out of visual range return( 0 ); } ddHorizAngle = atan2( dDeltaY, dDeltaX ); #ifdef LOS_DEBUG memset( &gLOSTestResults, 0, sizeof( LOSResults ) ); gLOSTestResults.fLOSTestPerformed = TRUE; gLOSTestResults.iStartX = (INT32) dStartX; gLOSTestResults.iStartY = (INT32) dStartY; gLOSTestResults.iStartZ = (INT32) dStartZ; gLOSTestResults.iEndX = (INT32) dEndX; gLOSTestResults.iEndY = (INT32) dEndY; gLOSTestResults.iEndZ = (INT32) dEndZ; gLOSTestResults.iMaxDistance = (INT32) iSightLimit; gLOSTestResults.iDistance = (INT32) dDistance; #endif qIncrX = FloatToFixed( dDeltaX / (FLOAT)iDistance ); qIncrY = FloatToFixed( dDeltaY / (FLOAT)iDistance ); qIncrZ = FloatToFixed( dDeltaZ / (FLOAT)iDistance ); fCheckForRoof = FALSE; // figure out starting and ending cubes iGridNo = GETWORLDINDEXFROMWORLDCOORDS( (INT32)dStartY, (INT32)dStartX ); qCurrZ = FloatToFixed( dStartZ ); qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ iGridNo ].sHeight ) ); iCurrAboveLevelZ = FIXEDPT_TO_INT32( qCurrZ - qLandHeight ); iStartCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ ); iStartCubesAboveLevelZ = iStartCubesZ; if (iStartCubesAboveLevelZ >= STRUCTURE_ON_GROUND_MAX) { iStartCubesAboveLevelZ -= STRUCTURE_ON_ROOF; } // check to see if we need to check for roofs based on the starting gridno qWallHeight = gqStandardWallHeight + qLandHeight; if ( qCurrZ < qWallHeight ) { // possibly going up through a roof on this level qCurrZ = FloatToFixed( dEndZ ); if ( qCurrZ > qWallHeight ) { fCheckForRoof = TRUE; } } else // > { // possibly going down through a roof on this level qCurrZ = FloatToFixed( dEndZ ); if (qCurrZ < qWallHeight) { fCheckForRoof = TRUE; } } iGridNo = GETWORLDINDEXFROMWORLDCOORDS( (INT32)dEndY, (INT32)dEndX ); qCurrZ = FloatToFixed( dEndZ ); qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ iGridNo ].sHeight ) ); iCurrAboveLevelZ = FIXEDPT_TO_INT32( qCurrZ - qLandHeight ); iEndCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ ); iEndCubesAboveLevelZ = iEndCubesZ; if (iEndCubesAboveLevelZ >= STRUCTURE_ON_GROUND_MAX) { iEndCubesAboveLevelZ -= STRUCTURE_ON_ROOF; } // check to see if we need to check for roofs based on the starting gridno qWallHeight = gqStandardWallHeight + qLandHeight; if ( qCurrZ < qWallHeight ) { // possibly going down through a roof on this level qCurrZ = FloatToFixed( dStartZ ); if ( qCurrZ > qWallHeight ) { fCheckForRoof = TRUE; } } else // > { // possibly going up through a roof on this level qCurrZ = FloatToFixed( dStartZ ); if (qCurrZ < qWallHeight) { fCheckForRoof = TRUE; } } // apply increments for first move // first move will be 1 step // plus a fractional part equal to half of the difference between the delta and // the increment times the distance qCurrX = FloatToFixed( dStartX ) + qIncrX + ( FloatToFixed( dDeltaX ) - qIncrX * iDistance ) / 2; qCurrY = FloatToFixed( dStartY ) + qIncrY + ( FloatToFixed( dDeltaY ) - qIncrY * iDistance ) / 2; qCurrZ = FloatToFixed( dStartZ ) + qIncrZ + ( FloatToFixed( dDeltaZ ) - qIncrZ * iDistance ) / 2; iCurrTileX = FIXEDPT_TO_TILE_NUM( qCurrX ); iCurrTileY = FIXEDPT_TO_TILE_NUM( qCurrY ); bLOSIndexX = FIXEDPT_TO_LOS_INDEX( qCurrX ); bLOSIndexY = FIXEDPT_TO_LOS_INDEX( qCurrY ); iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( qCurrZ ) ); iLoop = 1; do { // check a particular tile // retrieve values from world for this particular tile iGridNo = iCurrTileX + iCurrTileY * WORLD_COLS; pMapElement = &(gpWorldLevelData[ iGridNo ]); qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) ); qWallHeight = gqStandardWallHeight + qLandHeight; if (fCheckForRoof) { pRoofStructure = FindStructure( (INT16) iGridNo, STRUCTURE_ROOF ); if ( pRoofStructure ) { qLastZ = qCurrZ - qIncrZ; // if just on going to next tile we cross boundary, then roof stops sight here! if ( (qLastZ > qWallHeight && qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && qCurrZ >= qWallHeight)) { // hit a roof return( 0 ); } } } // record old tile location for loop purposes iOldTileX = iCurrTileX; iOldTileY = iCurrTileY; do { // check a particular location within the tile // check for collision with the ground iCurrAboveLevelZ = FIXEDPT_TO_INT32( qCurrZ - qLandHeight ); if (iCurrAboveLevelZ < 0) { // ground is in the way! #ifdef LOS_DEBUG gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX ); gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY ); gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ ); // subtract one to compensate for rounding up when negative gLOSTestResults.iCurrCubesZ = 0; #endif return( 0 ); } // check for the existence of structures pStructure = pMapElement->pStructureHead; if (pStructure == NULL) { // no structures in this tile, AND THAT INCLUDES ROOFS! :-) // new system; figure out how many steps until we cross the next edge // and then fast forward that many steps. iOldTileX = iCurrTileX; iOldTileY = iCurrTileY; iOldCubesZ = iCurrCubesZ; if (qIncrX > 0) { qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE )); iStepsToTravelX = qDistToTravelX / qIncrX; } else if (qIncrX < 0) { qDistToTravelX = qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ); iStepsToTravelX = qDistToTravelX / -qIncrX; } else { // make sure we don't consider X a limit :-) iStepsToTravelX = 1000000; } if (qIncrY > 0) { qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE )); iStepsToTravelY = qDistToTravelY / qIncrY; } else if (qIncrY < 0) { qDistToTravelY = qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ); iStepsToTravelY = qDistToTravelY / -qIncrY; } else { // make sure we don't consider Y a limit :-) iStepsToTravelY = 1000000; } iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1; /* if (qIncrX > 0) { qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE )); iStepsToTravelX = qDistToTravelX / qIncrX; } else if (qIncrX < 0) { qDistToTravelX = qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ); iStepsToTravelX = qDistToTravelX / (-qIncrX); } else { // make sure we don't consider X a limit :-) iStepsToTravelX = 1000000; } if (qIncrY > 0) { qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE )); iStepsToTravelY = qDistToTravelY / qIncrY; } else if (qIncrY < 0) { qDistToTravelY = qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ); iStepsToTravelY = qDistToTravelY / (-qIncrY); } else { // make sure we don't consider Y a limit :-) iStepsToTravelY = 1000000; } // add 1 to the # of steps to travel to go INTO the next tile iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1; //iStepsToTravel = 1; */ qCurrX += qIncrX * iStepsToTravel; qCurrY += qIncrY * iStepsToTravel; qCurrZ += qIncrZ * iStepsToTravel; iLoop += iStepsToTravel; // check for ground collision if (qCurrZ < qLandHeight && iLoop < iDistance) { // ground is in the way! #ifdef LOS_DEBUG gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX ); gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY ); gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ ); // subtract one to compensate for rounding up when negative gLOSTestResults.iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrCubesAboveLevelZ ) - 1; #endif return( 0 ); } // figure out the new tile location iCurrTileX = FIXEDPT_TO_TILE_NUM( qCurrX ); iCurrTileY = FIXEDPT_TO_TILE_NUM( qCurrY ); iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( qCurrZ ) ); bLOSIndexX = FIXEDPT_TO_LOS_INDEX( qCurrX ); bLOSIndexY = FIXEDPT_TO_LOS_INDEX( qCurrY ); } else { // there are structures in this tile iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ ); // figure out the LOS cube level of the current point if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX) { if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX) { // check objects on the ground sDesiredLevel = STRUCTURE_ON_GROUND; } else { // check objects on roofs sDesiredLevel = STRUCTURE_ON_ROOF; iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF; } // check structures for collision while (pStructure != NULL) { // transparent structures should be skipped // normal roof structures should be skipped here because their only bits are roof lips // and those should act as transparent fOpaque = (pStructure->fFlags & STRUCTURE_TRANSPARENT) == 0; if ( pStructure->fFlags & STRUCTURE_ROOF ) { // roof lip; allow sighting if person on roof is near if ( (iLoop < 2 * CELL_X_SIZE || (iDistance - iLoop) < 2 * CELL_X_SIZE ) ) { if ( iLoop <= CELL_X_SIZE + 1 || (iDistance - iLoop ) <= CELL_X_SIZE + 1 ) { // right near edge, allow sighting at 3 tiles from roof edge if prone // less if standing, and we can tell that with iStartCubesZ and iEndCubesZ if ( iStartCubesZ < iEndCubesZ ) { // looking up, so reduce for the target stance-height according to iEndCubesZ if ( iDistance >= (3 - iEndCubesAboveLevelZ) * CELL_X_SIZE ) { fOpaque = FALSE; } } else { if ( iDistance >= (3 - iStartCubesAboveLevelZ) * CELL_X_SIZE ) { fOpaque = FALSE; } } } else { if ( iDistance >= 12 * CELL_X_SIZE ) { fOpaque = FALSE; } } } } if ( fOpaque ) { if (pStructure->sCubeOffset == sDesiredLevel) { if (((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0) { if (fSmell) { if (pStructure->fFlags & STRUCTURE_TREE) { // smell not stopped by vegetation } else if ((pStructure->fFlags & STRUCTURE_WALLNWINDOW))// && (pStructure->fFlags & STRUCTURE_OPEN)) { // open/closed window, smell not stopped } else { if (pStructure->fFlags & STRUCTURE_WALLSTUFF) { DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("LineOfSightTest: checking thru wall, thermal = %d",fSmell)); // possibly at corner in which case we should let it pass fResolveHit = ResolveHitOnWall( pStructure, iGridNo, bLOSIndexX, bLOSIndexY, ddHorizAngle ); } else { fResolveHit = TRUE; } if (fResolveHit) { // CJC, May 30: smell reduced by obstacles but not stopped // if obstacle within 10 tiles iAdjSightLimit -= SMELL_REDUCTION_FOR_NEARBY_OBSTACLE; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("LineOfSightTest: hit a wall, thermal = %d, sight limit = %d, distance = %d",fSmell,iAdjSightLimit,iDistance)); if (iLoop > 100 || iDistance > iAdjSightLimit) { DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("LineOfSightTest: couldn't see thru wall, thermal = %d",fSmell)); // out of visual range #ifdef LOS_DEBUG gLOSTestResults.fOutOfRange = TRUE; gLOSTestResults.iCurrCubesZ = iCurrCubesZ; #endif return( 0 ); } /* // smell-line stopped by obstacle! #ifdef LOS_DEBUG gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX ); gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY ); gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ ); gLOSTestResults.iCurrCubesZ = iCurrCubesAboveLevelZ; #endif return( 0 ); */ } } } else { if (pStructure->fFlags & STRUCTURE_TREE) { // don't count trees close to the person if (iLoop > CLOSE_TO_FIRER) { if (iLoop > 100) { // at longer range increase the value of tree cover iAdjSightLimit -= (ubTreeSightReduction * iLoop) / 100; } else { // use standard value iAdjSightLimit -= ubTreeSightReduction; } #ifdef LOS_DEBUG gLOSTestResults.ubTreeSpotsHit++; gLOSTestResults.iMaxDistance = iSightLimit; #endif if (iDistance > iAdjSightLimit) { // out of visual range #ifdef LOS_DEBUG gLOSTestResults.fOutOfRange = TRUE; gLOSTestResults.iCurrCubesZ = iCurrCubesZ; #endif return( 0 ); } } } else if ( (pStructure->fFlags & STRUCTURE_WALLNWINDOW) && !(pStructure->fFlags & STRUCTURE_SPECIAL) && qCurrZ >= (gqStandardWindowBottomHeight + qLandHeight) && qCurrZ <= (gqStandardWindowTopHeight + qLandHeight)) { // do nothing; windows are transparent (except ones marked as special) if (psWindowGridNo != NULL) { // we're supposed to note the location of this window! // but if a location has already been set then there are two windows, in which case // we abort if (*psWindowGridNo == NOWHERE) { *psWindowGridNo = (INT16) iGridNo; return( iLoop ); } else { //*psWindowGridNo = NOWHERE; //return( iLoop ); } } } else { if (pStructure->fFlags & STRUCTURE_WALLSTUFF) { // possibly shooting at corner in which case we should let it pass fResolveHit = ResolveHitOnWall( pStructure, iGridNo, bLOSIndexX, bLOSIndexY, ddHorizAngle ); } else { if (iCurrCubesAboveLevelZ < (STANDING_CUBES - 1) ) { if ( (iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= iStartCubesAboveLevelZ) ) { // if we are in the same vertical cube as the start, // and this is the height of the structure, then allow sight to go through // NB cubes are 0 based, heights 1 based iStructureHeight = StructureHeight( pStructure ); fResolveHit = ( iCurrCubesAboveLevelZ != (iStructureHeight - 1) ); } else if ( (iLoop >= (iDistance - CLOSE_TO_FIRER) ) && (iCurrCubesAboveLevelZ <= iEndCubesZ) && bAware ) { // if we are in the same vertical cube as our destination, // and this is the height of the structure, and we are aware // then allow sight to go through // NB cubes are 0 based, heights 1 based iStructureHeight = StructureHeight( pStructure ); fResolveHit = ( iCurrCubesAboveLevelZ != (iStructureHeight - 1) ); } else { fResolveHit = TRUE; } } else { fResolveHit = TRUE; } } if (fResolveHit) { // hit the obstacle! #ifdef LOS_DEBUG gLOSTestResults.iStoppedX = FIXEDPT_TO_INT32( qCurrX ); gLOSTestResults.iStoppedY = FIXEDPT_TO_INT32( qCurrY ); gLOSTestResults.iStoppedZ = FIXEDPT_TO_INT32( qCurrZ ); gLOSTestResults.iCurrCubesZ = iCurrCubesAboveLevelZ; #endif return( 0 ); } } } } } } pStructure = pStructure->pNext; } } // got past all structures; go to next location within // tile, horizontally or vertically bOldLOSIndexX = bLOSIndexX; bOldLOSIndexY = bLOSIndexY; iOldCubesZ = iCurrCubesZ; do { qCurrX += qIncrX; qCurrY += qIncrY; if (pRoofStructure) { qLastZ = qCurrZ; qCurrZ += qIncrZ; if ( (qLastZ > qWallHeight && qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && qCurrZ >= qWallHeight)) { // hit a roof return( 0 ); } } else { qCurrZ += qIncrZ; } iLoop++; bLOSIndexX = FIXEDPT_TO_LOS_INDEX( qCurrX ); bLOSIndexY = FIXEDPT_TO_LOS_INDEX( qCurrY ); iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( qCurrZ ) ); // shouldn't need to check whether we are not at maximum range because // that will be caught below and this loop shouldn't go for more than a // couple of iterations. } while( (bLOSIndexX == bOldLOSIndexX) && (bLOSIndexY == bOldLOSIndexY) && (iCurrCubesZ == iOldCubesZ)); iCurrTileX = FIXEDPT_TO_TILE_NUM( qCurrX ); iCurrTileY = FIXEDPT_TO_TILE_NUM( qCurrY ); } } while( (iCurrTileX == iOldTileX) && (iCurrTileY == iOldTileY) && (iLoop < iDistance)); // leaving a tile, check to see if it had gas in it // if ( pMapElement->ubExtFlags[0] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS) ) if ( pMapElement->ubExtFlags[0] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS | MAPELEMENT_EXT_BURNABLEGAS) ) { if ( (pMapElement->ubExtFlags[0] & MAPELEMENT_EXT_SMOKE) && !fSmell ) { bSmoke++; // we can only see 3 tiles in smoke // (2 if we're IN smoke) if ( bSmoke >= 3 ) { iAdjSightLimit = 0; } // unpopular /* else { // losing 1/3rd results in chances to hit which are WAY too low when firing from out of // two tiles of smoke... changing this to a 1/6 penalty iAdjSightLimit -= iSightLimit / 6; } */ } else { // reduce by 2 tiles per tile of tear gas or mustard gas iAdjSightLimit -= 2 * CELL_X_SIZE; } if ( iAdjSightLimit <= 0 ) { // can't see, period! return( 0 ); } } } while( iLoop < iDistance ); // unless the distance is integral, after the loop there will be a // fractional amount of distance remaining which is unchecked // but we shouldn't(?) need to check it because the target is there! #ifdef LOS_DEBUG gLOSTestResults.fLOSClear = TRUE; #endif //ADB this was the original code and comments: // this somewhat complicated formula does the following: // it starts with the distance to the target // it adds the difference between the original and adjusted sight limit, = the amount of cover // it then scales the value based on the difference between the original sight limit and the // very maximum possible in best lighting conditions //return( (iDistance + (iSightLimit - iAdjSightLimit)) * (MaxDistanceVisible() * CELL_X_SIZE) / iSightLimit ); //in the original code, iSightLimit could vary depending exactly on what I can see //so assuming there was no cover, the returned value is altered exactly on iSightLimit //if my sight is low, then the function returns a value higher than MaxNormalDistanceVisible //this simulates a target farther away and harder to hit //if my sight is higher than the max distance, via sniper scope etc, then the function returns a lower value //this makes the target seem like it is closer and easier to hit //ADB this is the new code //the problem with the new code is there needs to be a way to calculate a line //without stopping when my eyes stop, but also including my sight limit! //so iTileSightLimit has 255 added to it if the line is supposed to be infinite int distanceWithCover = (iDistance + (iSightLimit - iAdjSightLimit)); if (adjustForSight == false) { //I know I am doing a CTGT calc, so don't get carried away //just because I can see 999 tiles doesn't mean I can shoot it! //this preserves the original intent of the scalar, but makes it less likely iAdjSightLimit //is less than zero, which if it were the function would return, which we don't want if our sight is good return distanceWithCover; } //I know that my sight could be over the average distance, but I am not going to clamp it //because I do not think the actual value matters, and if it does, //then using the real value is closer to the original intent //AFAIK all instances that aren't CTGT just use this as a boolean test return( distanceWithCover * (MaxNormalDistanceVisible() * CELL_X_SIZE) / iSightLimit ); } BOOLEAN CalculateSoldierZPos( SOLDIERTYPE * pSoldier, UINT8 ubPosType, FLOAT * pdZPos ) { UINT8 ubHeight; if ( pSoldier->ubBodyType == CROW ) { // Crow always as prone... ubHeight = ANIM_PRONE; } else if ( pSoldier->MercInDeepWater( ) ) { // treat as prone ubHeight = ANIM_PRONE; } else if ( pSoldier->MercInShallowWater( ) ) { // treat as crouched ubHeight = ANIM_CROUCH; } else { if ( CREATURE_OR_BLOODCAT( pSoldier ) || pSoldier->ubBodyType == COW ) { // this if statement is to avoid the 'creature weak spot' target // spot for creatures if ( ubPosType == HEAD_TARGET_POS || ubPosType == LEGS_TARGET_POS ) { // override! ubPosType = TORSO_TARGET_POS; } } else if ( TANK( pSoldier ) ) { // high up! ubPosType = HEAD_TARGET_POS; } ubHeight = gAnimControl[ pSoldier->usAnimState ].ubEndHeight; } switch( ubPosType ) { case LOS_POS: switch ( ubHeight ) { case ANIM_STAND: *pdZPos = STANDING_LOS_POS; break; case ANIM_CROUCH: *pdZPos = CROUCHED_LOS_POS; break; case ANIM_PRONE: *pdZPos = PRONE_LOS_POS; break; default: return( FALSE ); } break; case FIRING_POS: switch ( ubHeight ) { case ANIM_STAND: *pdZPos = STANDING_FIRING_POS; break; case ANIM_CROUCH: *pdZPos = CROUCHED_FIRING_POS; break; case ANIM_PRONE: *pdZPos = PRONE_FIRING_POS; break; default: return( FALSE ); } break; case TARGET_POS: switch ( ubHeight ) { case ANIM_STAND: *pdZPos = STANDING_TARGET_POS; break; case ANIM_CROUCH: *pdZPos = CROUCHED_TARGET_POS; break; case ANIM_PRONE: *pdZPos = PRONE_TARGET_POS; break; default: return( FALSE ); } break; case HEAD_TARGET_POS: switch ( ubHeight ) { case ANIM_STAND: *pdZPos = STANDING_HEAD_TARGET_POS; break; case ANIM_CROUCH: *pdZPos = CROUCHED_HEAD_TARGET_POS; break; case ANIM_PRONE: *pdZPos = PRONE_HEAD_TARGET_POS; break; default: return( FALSE ); } break; case TORSO_TARGET_POS: switch ( ubHeight ) { case ANIM_STAND: *pdZPos = STANDING_TORSO_TARGET_POS; break; case ANIM_CROUCH: *pdZPos = CROUCHED_TORSO_TARGET_POS; break; case ANIM_PRONE: *pdZPos = PRONE_TORSO_TARGET_POS; break; default: return( FALSE ); } break; case LEGS_TARGET_POS: switch ( ubHeight ) { case ANIM_STAND: *pdZPos = STANDING_LEGS_TARGET_POS; break; case ANIM_CROUCH: *pdZPos = CROUCHED_LEGS_TARGET_POS; break; case ANIM_PRONE: *pdZPos = PRONE_LEGS_TARGET_POS; break; default: return( FALSE ); } break; case HEIGHT: switch ( ubHeight ) { case ANIM_STAND: *pdZPos = STANDING_HEIGHT; break; case ANIM_CROUCH: *pdZPos = CROUCHED_HEIGHT; break; case ANIM_PRONE: *pdZPos = PRONE_HEIGHT; break; default: return( FALSE ); } break; } if ( pSoldier->ubBodyType == HATKIDCIV || pSoldier->ubBodyType == KIDCIV ) { // reduce value for kids who are 2/3 the height of regular people *pdZPos = (*pdZPos * 2) / 3; } else if ( pSoldier->ubBodyType == ROBOTNOWEAPON || pSoldier->ubBodyType == LARVAE_MONSTER || pSoldier->ubBodyType == INFANT_MONSTER || pSoldier->ubBodyType == BLOODCAT ) { // robot is 1/3 the height of regular people *pdZPos = *pdZPos / 3; } else if ( TANK ( pSoldier ) ) { *pdZPos = (*pdZPos * 4) / 3; } if (pSoldier->pathing.bLevel > 0) { // on a roof *pdZPos += WALL_HEIGHT_UNITS; } // IF this is a plane, strafe! // ATE: Don;t panic - this is temp - to be changed to a status flag.... if ( pSoldier->ubID == MAX_NUM_SOLDIERS ) { *pdZPos = ( WALL_HEIGHT_UNITS * 2 ) - 1; } *pdZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pSoldier->sGridNo].sHeight ); return( TRUE ); } INT32 SoldierToSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, INT8 bAware, int iTileSightLimit, UINT8 ubAimLocation, bool adjustForSight ) { FLOAT dStartZPos, dEndZPos; BOOLEAN fOk; BOOLEAN fSmell; INT16 bEffectiveCamo; INT16 bEffectiveStealth; UINT8 ubTreeReduction; UINT8 ubPosType; // TO ADD: if target is camouflaged and in cover, reduce sight distance by 30% // TO ADD: if in tear gas, reduce sight limit to 2 tiles CHECKF( pStartSoldier ); CHECKF( pEndSoldier ); fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos ); CHECKF( fOk ); if ( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_N ) { // in the bloodcat arena sector, skip sight between army & bloodcats if ( pStartSoldier->bTeam == ENEMY_TEAM && pEndSoldier->bTeam == CREATURE_TEAM ) { return( 0 ); } if ( pStartSoldier->bTeam == CREATURE_TEAM && pEndSoldier->bTeam == ENEMY_TEAM ) { return( 0 ); } } if (pStartSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) { // monsters use smell instead of sight! dEndZPos = STANDING_LOS_POS; // should avoid low rocks etc if (pEndSoldier->pathing.bLevel > 0) { // on a roof dEndZPos += WALL_HEIGHT_UNITS; } dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pEndSoldier->sGridNo].sHeight ); fSmell = TRUE; } else { switch( ubAimLocation ) { case AIM_SHOT_HEAD: ubPosType = HEAD_TARGET_POS; break; case AIM_SHOT_TORSO: ubPosType = TORSO_TARGET_POS; break; case AIM_SHOT_LEGS: ubPosType = LEGS_TARGET_POS; break; default: ubPosType = TARGET_POS; break; } fOk = CalculateSoldierZPos( pEndSoldier, ubPosType, &dEndZPos ); if (!fOk) { return( FALSE ); } CHECKF( fOk ); fSmell = HasThermalOptics( pStartSoldier); } if ( TANK( pStartSoldier ) ) { INT16 sDistance; sDistance = PythSpacesAway( pStartSoldier->sGridNo, pEndSoldier->sGridNo ); if ( sDistance <= 8 ) { // blind spot? if ( dEndZPos <= PRONE_LOS_POS ) { return( FALSE ); } else if ( sDistance <= 4 && dEndZPos <= CROUCHED_LOS_POS ) { return( FALSE ); } } } //get the total camouflage int jungle = max(1,min(100,pEndSoldier->bCamo + pEndSoldier->wornCamo)); int urban = max(1,min(100,pEndSoldier->urbanCamo + pEndSoldier->wornUrbanCamo)); int desert = max(1,min(100,pEndSoldier->desertCamo + pEndSoldier->wornDesertCamo)); int snow = max(1,min(100,pEndSoldier->snowCamo + pEndSoldier->wornSnowCamo)); int totalCamo = max(1,min(100,jungle + urban + desert + snow)); if ( ( totalCamo ) > 0 && !bAware ) { // reduce effects of camo of 5% per tile moved last turn if ( pEndSoldier->ubBodyType == BLOODCAT ) { bEffectiveCamo = 100 - pEndSoldier->bTilesMoved * 5; } else { bEffectiveCamo = max(0,min(100,(totalCamo))) * (100 - pEndSoldier->bTilesMoved * 5) / 100; } bEffectiveCamo = __max( bEffectiveCamo, 0 ); if ( gAnimControl[ pEndSoldier->usAnimState ].ubEndHeight < ANIM_STAND ) { // reduce visibility by up to a third for camouflage! switch( pEndSoldier->bOverTerrainType ) { case LOW_GRASS: case HIGH_GRASS: // jungle camo bonus iTileSightLimit -= iTileSightLimit * (jungle * bEffectiveCamo / 3 / totalCamo) / 100; break; case FLAT_FLOOR: // flat floor = indoors case PAVED_ROAD: // urban camo bonus iTileSightLimit -= iTileSightLimit * (urban * bEffectiveCamo / 3 / totalCamo) / 100; break; case DIRT_ROAD: // desert camo bonus case TRAIN_TRACKS: iTileSightLimit -= iTileSightLimit * (desert * bEffectiveCamo / 3 / totalCamo) / 100; break; //case ??? : // snow camo bonus // iTileSightLimit -= iTileSightLimit * (snow * bEffectiveCamo / 3 / totalCamo) / 100; // break; case FLAT_GROUND: //in this case both desert and jungle can work: iTileSightLimit -= iTileSightLimit * (jungle * bEffectiveCamo / 3 / totalCamo) / 100; iTileSightLimit -= iTileSightLimit * (desert * bEffectiveCamo / 3 / totalCamo) / 100; break; default: break; } } } else { bEffectiveCamo = 0; } //Madd: added - note stealth is capped at 100 just like camo if ( ( GetWornStealth(pEndSoldier) ) > 0 && !bAware ) { // reduce effects of stealth by 5% per tile moved last turn bEffectiveStealth = max(0,min(100,(GetWornStealth(pEndSoldier)))) * (100 - pEndSoldier->bTilesMoved * 5) / 100; bEffectiveStealth = __max( bEffectiveStealth, 0 ); // reduce visibility by up to a third for Stealth! // stance and terrain don't matter iTileSightLimit -= iTileSightLimit * (bEffectiveStealth / 3) / 100; } else { bEffectiveStealth = 0; } if ( TANK( pEndSoldier ) ) { ubTreeReduction = 0; } else { ubTreeReduction = gubTreeSightReduction[ gAnimControl[pEndSoldier->usAnimState].ubEndHeight ]; } if (iTileSightLimit == CALC_FROM_ALL_DIRS || iTileSightLimit == CALC_FROM_WANTED_DIR) { iTileSightLimit = pStartSoldier->GetMaxDistanceVisible( pEndSoldier->sGridNo, pEndSoldier->pathing.bLevel, iTileSightLimit ); } else if (iTileSightLimit == NO_DISTANCE_LIMIT) { iTileSightLimit = 255 + pStartSoldier->GetMaxDistanceVisible( pEndSoldier->sGridNo, pEndSoldier->pathing.bLevel, CALC_FROM_ALL_DIRS ); } return( LineOfSightTest( (FLOAT) CenterX( pStartSoldier->sGridNo ), (FLOAT) CenterY( pStartSoldier->sGridNo ), dStartZPos, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos, iTileSightLimit, ubTreeReduction, bAware, bEffectiveCamo + bEffectiveStealth, fSmell, NULL, adjustForSight ) ); } INT16 SoldierToLocationWindowTest( SOLDIERTYPE * pStartSoldier, INT16 sEndGridNo ) { // figure out if there is a SINGLE window between the looker and target FLOAT dStartZPos, dEndZPos; INT16 sXPos, sYPos, sWindowGridNo = NOWHERE; INT32 iRet; CHECKF( pStartSoldier ); dStartZPos = FixedToFloat( ((gqStandardWindowTopHeight + gqStandardWindowBottomHeight) / 2) ); if (pStartSoldier->pathing.bLevel > 0) { // on a roof dStartZPos += WALL_HEIGHT_UNITS; } dStartZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pStartSoldier->sGridNo].sHeight ); dEndZPos = dStartZPos; ConvertGridNoToXY( sEndGridNo, &sXPos, &sYPos ); sXPos = sXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sYPos = sYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); //ADB changed from 255 to 511 to handle new LOS test // We don't want to consider distance limits here so pass in tile sight limit of 255( + 256) // and consider trees as little as possible iRet = LineOfSightTest( (FLOAT) CenterX( pStartSoldier->sGridNo ), (FLOAT) CenterY( pStartSoldier->sGridNo ), dStartZPos, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos, 511, 0, TRUE, 0, FALSE, &sWindowGridNo ); return( sWindowGridNo ); } INT32 SoldierTo3DLocationLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bCubeLevel, INT8 bAware, int iTileSightLimit, bool adjustForSight ) { FLOAT dStartZPos, dEndZPos; INT16 sXPos, sYPos; UINT8 ubTargetID; BOOLEAN fOk; CHECKF( pStartSoldier ); fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos ); CHECKF( fOk ); if (bCubeLevel > 0) { dEndZPos = ((FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE ) - 0.5f) * HEIGHT_UNITS_PER_INDEX; dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sGridNo ].sHeight ); } else { ubTargetID = WhoIsThere2( sGridNo, bLevel ); if (ubTargetID != NOBODY) { // there's a merc there; do a soldier-to-soldier test return( SoldierToSoldierLineOfSightTest( pStartSoldier, MercPtrs[ubTargetID], bAware, iTileSightLimit, LOS_POS, adjustForSight) ); } // else... assume standing height dEndZPos = STANDING_LOS_POS + bLevel * HEIGHT_UNITS; // add in ground height dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sGridNo ].sHeight ); } ConvertGridNoToXY( sGridNo, &sXPos, &sYPos ); sXPos = sXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sYPos = sYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); if (iTileSightLimit == CALC_FROM_ALL_DIRS || iTileSightLimit == CALC_FROM_WANTED_DIR) { iTileSightLimit = pStartSoldier->GetMaxDistanceVisible( sGridNo, bLevel, iTileSightLimit ); } else if (iTileSightLimit == NO_DISTANCE_LIMIT) { iTileSightLimit = 255 + pStartSoldier->GetMaxDistanceVisible( sGridNo, bLevel, CALC_FROM_ALL_DIRS ); } return( LineOfSightTest( (FLOAT) CenterX( pStartSoldier->sGridNo ), (FLOAT) CenterY( pStartSoldier->sGridNo ), dStartZPos, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos, iTileSightLimit, gubTreeSightReduction[ANIM_STAND], bAware, 0, HasThermalOptics( pStartSoldier), NULL, adjustForSight ) ); } INT32 SoldierToVirtualSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bStance, INT8 bAware, int iTileSightLimit ) { FLOAT dStartZPos, dEndZPos; INT16 sXPos, sYPos; BOOLEAN fOk; CHECKF( pStartSoldier ); fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos ); CHECKF( fOk ); // manually calculate destination Z position. switch( bStance ) { case ANIM_STAND: dEndZPos = STANDING_LOS_POS; break; case ANIM_CROUCH: dEndZPos = CROUCHED_LOS_POS; break; case ANIM_PRONE: dEndZPos = PRONE_LOS_POS; break; default: return( FALSE ); } dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight ); if (bLevel > 0) { // on a roof dEndZPos += WALL_HEIGHT_UNITS; } ConvertGridNoToXY( sGridNo, &sXPos, &sYPos ); sXPos = sXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sYPos = sYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); if (iTileSightLimit == CALC_FROM_ALL_DIRS || iTileSightLimit == CALC_FROM_WANTED_DIR) { iTileSightLimit = pStartSoldier->GetMaxDistanceVisible( sGridNo, bLevel, iTileSightLimit ); } else if (iTileSightLimit == NO_DISTANCE_LIMIT) { iTileSightLimit = 255 + pStartSoldier->GetMaxDistanceVisible( sGridNo, bLevel, CALC_FROM_ALL_DIRS ); } return( LineOfSightTest( (FLOAT) CenterX( pStartSoldier->sGridNo ), (FLOAT) CenterY( pStartSoldier->sGridNo ), dStartZPos, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos, iTileSightLimit, gubTreeSightReduction[ANIM_STAND], bAware, 0, HasThermalOptics( pStartSoldier), NULL ) ); } INT32 LocationToLocationLineOfSightTest( INT16 sStartGridNo, INT8 bStartLevel, INT16 sEndGridNo, INT8 bEndLevel, INT8 bAware, int iTileSightLimit ) { FLOAT dStartZPos, dEndZPos; INT16 sStartXPos, sStartYPos, sEndXPos, sEndYPos; UINT8 ubStartID; ubStartID = WhoIsThere2( sStartGridNo, bStartLevel ); if ( ubStartID != NOBODY ) { return( SoldierTo3DLocationLineOfSightTest( MercPtrs[ ubStartID ], sEndGridNo, bEndLevel, 0, bAware, iTileSightLimit ) ); } // else... assume standing heights dStartZPos = STANDING_LOS_POS + bStartLevel * HEIGHT_UNITS; // add in ground height dStartZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sStartGridNo ].sHeight ); ConvertGridNoToXY( sStartGridNo, &sStartXPos, &sStartYPos ); sStartXPos = sStartXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sStartYPos = sStartYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); dEndZPos = STANDING_LOS_POS + bEndLevel * HEIGHT_UNITS; // add in ground height dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ sEndGridNo ].sHeight ); ConvertGridNoToXY( sEndGridNo, &sEndXPos, &sEndYPos ); sEndXPos = sEndXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sEndYPos = sEndYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); if (iTileSightLimit == CALC_FROM_ALL_DIRS || iTileSightLimit == CALC_FROM_WANTED_DIR) { iTileSightLimit = MaxNormalDistanceVisible(); } else if (iTileSightLimit == NO_DISTANCE_LIMIT) { iTileSightLimit = 255 + MaxNormalDistanceVisible(); } return( LineOfSightTest( (FLOAT)sStartXPos, (FLOAT)sStartYPos, dStartZPos, (FLOAT) sEndXPos, (FLOAT) sEndYPos, dEndZPos, iTileSightLimit, gubTreeSightReduction[ANIM_STAND], bAware, 0, FALSE, NULL ) ); } /* INT32 BulletImpactReducedByRange( INT32 iImpact, INT32 iDistanceTravelled, INT32 iRange ) { // for now, don't reduce, because did weird stuff to AI! return( iImpact ); // only start reducing impact at distances greater than one range //return( __max( 1, iImpact * ( 100 - ( PERCENT_BULLET_SLOWED_BY_RANGE * iDistanceTravelled ) / iRange ) / 100 ) ); } */ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntended ) { INT32 iImpact, iDamage; EV_S_WEAPONHIT SWeaponHit; INT16 sRange; SOLDIERTYPE * pFirer = pBullet->pFirer; FLOAT dZPosRelToMerc; UINT8 ubHitLocation = AIM_SHOT_RANDOM; UINT8 ubAttackDirection; UINT8 ubAmmoType; UINT32 uiChanceThrough; UINT8 ubSpecial = FIRE_WEAPON_NO_SPECIAL; INT16 sHitBy; BOOLEAN fStopped = TRUE; INT8 bSlot; INT8 bHeadSlot = NO_SLOT; SOLDIERTYPE * pTarget; INT16 sNewGridNo; BOOLEAN fCanSpewBlood = FALSE; INT8 bSpewBloodLevel; // structure IDs for mercs match their merc IDs pTarget = MercPtrs[ pStructure->usStructureID ]; if (pBullet->usFlags & BULLET_FLAG_KNIFE) { // Place knife on guy.... // See if they have room ( and make sure it's not in hand pos? // CHRISL: bSlot = FindEmptySlotWithin( pTarget, BIGPOCKSTART, NUM_INV_SLOTS ); if (bSlot == NO_SLOT) { // Add item CreateItem( pBullet->fromItem, (INT8) pBullet->ubItemStatus, &gTempObject ); AddItemToPool( pTarget->sGridNo, &gTempObject, -1 , pTarget->pathing.bLevel, 0, 0 ); // Make team look for items NotifySoldiersToLookforItems( ); } else { if ( Item[pBullet->fromItem].bloodieditem > 0 ) CreateItem( Item[pBullet->fromItem].bloodieditem, (INT8) pBullet->ubItemStatus, &(pTarget->inv[bSlot]) ); else CreateItem( pBullet->fromItem, (INT8) pBullet->ubItemStatus, &(pTarget->inv[bSlot]) ); } ubAmmoType = AMMO_KNIFE; } else { ubAmmoType = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType; } // at least partly compensate for "near miss" increases for this guy, after all, the bullet // actually hit him! // take this out for now at least... no longer certain that he was awarded a suppression pt // when the bullet got near him //pTarget->ubSuppressionPoints--; if ( pTarget->flags.uiStatusFlags & SOLDIER_VEHICLE || (pTarget->ubBodyType == COW || pTarget->ubBodyType == CROW || pTarget->ubBodyType == BLOODCAT) ) { //ubHitLocation = pStructure->ubVehicleHitLocation; ubHitLocation = AIM_SHOT_TORSO; } else { // Determine where the person was hit... if ( CREATURE_OR_BLOODCAT( pTarget ) ) { ubHitLocation = AIM_SHOT_TORSO; // adult monster types have a weak spot if ( (pTarget->ubBodyType >= ADULTFEMALEMONSTER) && (pTarget->ubBodyType <= YAM_MONSTER) ) { ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->pFirer->sGridNo, pTarget->sGridNo ); if ( ubAttackDirection == pTarget->ubDirection || ubAttackDirection == gOneCCDirection[ pTarget->ubDirection ] || ubAttackDirection == gOneCDirection[ pTarget->ubDirection ] ) { // may hit weak spot! if (0) // check fact { uiChanceThrough = 30; } else { uiChanceThrough = 1; } if (PreRandom( 100 ) < uiChanceThrough) { ubHitLocation = AIM_SHOT_GLAND; } } } } if (ubHitLocation == AIM_SHOT_RANDOM) // i.e. if not set yet { if (pTarget->MercInDeepWater( ) ) { // automatic head hit! ubHitLocation = AIM_SHOT_HEAD; } else { switch (gAnimControl[ pTarget->usAnimState ].ubEndHeight) { case ANIM_STAND: // Fall through to crouch if in shallow or medium water if ( !pTarget->MercInShallowWater( ) ) { dZPosRelToMerc = FixedToFloat( pBullet->qCurrZ ) - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pBullet->sGridNo].sHeight ); if ( dZPosRelToMerc > HEIGHT_UNITS ) { dZPosRelToMerc -= HEIGHT_UNITS; } if (dZPosRelToMerc > STANDING_HEAD_BOTTOM_POS) { ubHitLocation = AIM_SHOT_HEAD; } else if (dZPosRelToMerc < STANDING_TORSO_BOTTOM_POS ) { ubHitLocation = AIM_SHOT_LEGS; } else { ubHitLocation = AIM_SHOT_TORSO; } break; } case ANIM_CROUCH: dZPosRelToMerc = FixedToFloat( pBullet->qCurrZ ) - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pBullet->sGridNo].sHeight ); if ( dZPosRelToMerc > HEIGHT_UNITS ) { dZPosRelToMerc -= HEIGHT_UNITS; } if (dZPosRelToMerc > CROUCHED_HEAD_BOTTOM_POS) { ubHitLocation = AIM_SHOT_HEAD; } else if ( dZPosRelToMerc < CROUCHED_TORSO_BOTTOM_POS ) { // prevent targets in water from being hit in legs ubHitLocation = AIM_SHOT_LEGS; } else { ubHitLocation = AIM_SHOT_TORSO; } break; case ANIM_PRONE: ubHitLocation = AIM_SHOT_TORSO; break; } } } if ( ( AmmoTypes[ubAmmoType].monsterSpit ) && (ubHitLocation == AIM_SHOT_HEAD) && ( ! (pTarget->flags.uiStatusFlags & SOLDIER_MONSTER) ) ) { UINT8 ubOppositeDirection; ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->pFirer->sGridNo, pTarget->sGridNo ); ubOppositeDirection = gOppositeDirection[ ubAttackDirection ]; if ( ! ( ubOppositeDirection == pTarget->ubDirection || ubAttackDirection == gOneCCDirection[ pTarget->ubDirection ] || ubAttackDirection == gOneCDirection[ pTarget->ubDirection ] ) ) { // lucky bastard was facing away! } // else if ( ( (pTarget->inv[HEAD1POS].usItem == NIGHTGOGGLES) || (pTarget->inv[HEAD1POS].usItem == SUNGOGGLES) || (pTarget->inv[HEAD1POS].usItem == GASMASK) ) && ( PreRandom( 100 ) < (UINT32) (pTarget->inv[HEAD1POS][0]->data.objectStatus) ) ) else if ( ( (pTarget->inv[HEAD1POS].exists() == true) ) && ( PreRandom( 100 ) < (UINT32) (pTarget->inv[HEAD1POS][0]->data.objectStatus) ) ) { // lucky bastard was wearing protective stuff bHeadSlot = HEAD1POS; } else if ( ( (pTarget->inv[HEAD2POS].exists() == true) ) && ( PreRandom( 100 ) < (UINT32) (pTarget->inv[HEAD2POS][0]->data.objectStatus) ) ) { // lucky bastard was wearing protective stuff bHeadSlot = HEAD2POS; } else { // splat!! ubSpecial = FIRE_WEAPON_BLINDED_BY_SPIT_SPECIAL; } } } // Determine damage, checking guy's armour, etc sRange = (INT16)GetRangeInCellCoordsFromGridNoDiff( pFirer->sGridNo, pTarget->sGridNo ); if ( gTacticalStatus.uiFlags & GODMODE && !(pFirer->flags.uiStatusFlags & SOLDIER_PC)) { // in god mode, and firer is computer controlled iImpact = 0; iDamage = 0; } else if (fIntended) { if (pFirer->aiData.bOppList[pTarget->ubID] == SEEN_CURRENTLY) { sHitBy = pBullet->sHitBy; } else { // hard to aim at something far away being reported by someone else! sHitBy = pBullet->sHitBy / 2; } // hit the intended target which was in our LOS // reduce due to range iImpact = pBullet->iImpact; //BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange ); iImpact -= pBullet->iImpactReduction; if (iImpact < 0) { // shouldn't happen but iImpact = 0; } iDamage = BulletImpact( pFirer, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial ); // handle hit here... if( ( pFirer->bTeam == 0 ) ) { gMercProfiles[ pFirer->ubProfile ].usShotsHit++; } // intentionally shot pTarget->flags.fIntendedTarget = TRUE; if ( (pBullet->usFlags & BULLET_FLAG_BUCKSHOT) && ( pTarget->ubID == pFirer->ubTargetID ) ) { pTarget->bNumPelletsHitBy++; } } else { // if an accidental target was hit, don't give a bonus for good aim! sHitBy = 0; iImpact = pBullet->iImpact; //iImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange ); iImpact -= pBullet->iImpactReduction; if (iImpact < 0) { // shouldn't happen but iImpact = 0; } iDamage = BulletImpact( pFirer, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial ); // accidentally shot pTarget->flags.fIntendedTarget = FALSE; } if ( AmmoTypes[ubAmmoType].monsterSpit ) { if (bHeadSlot != NO_SLOT) { pTarget->inv[ bHeadSlot ][0]->data.objectStatus -= (INT8) ( (iImpact / 2) + Random( (iImpact / 2) ) ); if ( pTarget->inv[ bHeadSlot ][0]->data.objectStatus <= USABLE ) { if ( pTarget->inv[ bHeadSlot ][0]->data.objectStatus <= 0 ) { DeleteObj( &(pTarget->inv[ bHeadSlot ]) ); DirtyMercPanelInterface( pTarget, DIRTYLEVEL2 ); } // say curse? } } } else if (ubHitLocation == AIM_SHOT_HEAD) { // bullet to the head may damage any head item bHeadSlot = HEAD1POS + (INT8) Random( 2 ); if ( pTarget->inv[ bHeadSlot ].exists() == true ) { pTarget->inv[ bHeadSlot ][0]->data.objectStatus -= (INT8) ( Random( iImpact / 2 ) ); if ( pTarget->inv[ bHeadSlot ][0]->data.objectStatus < 0 ) { // just break it... pTarget->inv[ bHeadSlot ][0]->data.objectStatus = 1; } } } // check to see if the guy is a friendly?..if so, up the number of times wounded if( ( pTarget->bTeam == gbPlayerNum ) ) { gMercProfiles[ pTarget->ubProfile ].usTimesWounded++; } // check to see if someone was accidentally hit when no target was specified by the player if ( pFirer->bTeam == gbPlayerNum && pFirer->ubTargetID == NOBODY && pTarget->aiData.bNeutral ) { if ( pTarget->ubCivilianGroup == KINGPIN_CIV_GROUP || pTarget->ubCivilianGroup == HICKS_CIV_GROUP ) { // hicks and kingpin are touchy! pFirer->ubTargetID = pTarget->ubID; } else if ( Random( 100 ) < 60 ) { // get touchy pFirer->ubTargetID = pTarget->ubID; } } // Send event for getting hit memset( &(SWeaponHit), 0, sizeof( SWeaponHit ) ); SWeaponHit.usSoldierID = pTarget->ubID; SWeaponHit.uiUniqueId = pTarget->uiUniqueSoldierIdValue; SWeaponHit.usWeaponIndex = pFirer->usAttackingWeapon; SWeaponHit.sDamage = (INT16) iDamage; // breath loss is based on original impact of bullet SWeaponHit.sBreathLoss = (INT16) ( (iImpact * APBPConstants[BP_GET_WOUNDED] * (pTarget->bBreathMax * 100 - pTarget->sBreathRed)) / 10000 ); SWeaponHit.usDirection = GetDirectionFromGridNo( pFirer->sGridNo, pTarget ); SWeaponHit.sXPos = (INT16)pTarget->dXPos; SWeaponHit.sYPos = (INT16)pTarget->dYPos; SWeaponHit.sZPos = 20; SWeaponHit.sRange = sRange; SWeaponHit.ubAttackerID = pFirer->ubID; SWeaponHit.fHit = TRUE; SWeaponHit.ubLocation = ubHitLocation; if ( (pFirer->bDoBurst) && (ubSpecial == FIRE_WEAPON_NO_SPECIAL) ) { // the animation required by the bullet hit (head explosion etc) overrides the // hit-by-a-burst animation ubSpecial = FIRE_WEAPON_BURST_SPECIAL; } SWeaponHit.ubSpecial = ubSpecial; // now check to see if the bullet goes THROUGH this person! (not vehicles) if ( !(pTarget->flags.uiStatusFlags & SOLDIER_VEHICLE) && (AmmoTypes[ubAmmoType].canGoThrough) && !EXPLOSIVE_GUN( pFirer->usAttackingWeapon ) ) { // if we do more damage than expected, then the bullet will be more likely // to be lodged in the body // if we do less than expected, then the bullet has been slowed and less // likely to continue // higher chance for bigger guns, because they'll go through the back armour // reduce impact to match damage, if damage wasn't more than the impact // due to good aim, etc. if (iDamage < iImpact) { iImpact = iDamage; } uiChanceThrough = (UINT8) __max( 0, ( iImpact - 20 ) ); if (PreRandom( 100 ) < uiChanceThrough ) { // bullet MAY go through // adjust for bullet going through person iImpact -= CalcBodyImpactReduction( ubAmmoType, ubHitLocation ); // adjust for other side of armour! iImpact -= TotalArmourProtection( pFirer, pTarget, ubHitLocation, iImpact, ubAmmoType ); if (iImpact > 0) { pBullet->iImpact = (INT8) iImpact; // bullet was NOT stopped fStopped = FALSE; } } } if ( gTacticalStatus.ubCurrentTeam != OUR_TEAM && pTarget->bTeam == gbPlayerNum ) { // someone has been hit so no close-call quotes gTacticalStatus.fSomeoneHit = TRUE; } // handle hit! Handle damage before deleting the bullet though //hayden if(SWeaponHit.ubAttackerID < 20 ||(is_server && SWeaponHit.ubAttackerID < 120)|| !is_client ) // 124 last possible ai { if(is_client) SWeaponHit.sDamage=(INT16)(SWeaponHit.sDamage * DAMAGE_MULTIPLIER); // adjust damage from external variable //hayden WeaponHit( SWeaponHit.usSoldierID, SWeaponHit.usWeaponIndex, SWeaponHit.sDamage, SWeaponHit.sBreathLoss, SWeaponHit.usDirection, SWeaponHit.sXPos, SWeaponHit.sYPos, SWeaponHit.sZPos, SWeaponHit.sRange, SWeaponHit.ubAttackerID, SWeaponHit.fHit, SWeaponHit.ubSpecial, SWeaponHit.ubLocation ); if(is_server || (is_client && SWeaponHit.ubAttackerID <20) ) { SWeaponHit.fStopped=fStopped; SWeaponHit.iBullet=pBullet->iBullet; send_hit( &SWeaponHit ); } } if (fStopped) { RemoveBullet( pBullet->iBullet ); } else { // ATE: I'm in enemy territory again, evil CC's world :) // This looks like the place I should add code to spew blood on the ground // The algorithm I'm going to use is given the current gridno of bullet, // get a new gridno based on direction it was moving. Check to see if we're not // going through walls, etc by testing for a path, unless on the roof, in which case it would always // be legal, but the bLevel May change... sNewGridNo = NewGridNo( (INT16)pBullet->sGridNo, DirectionInc( gOppositeDirection[ SWeaponHit.usDirection ] ) ); bSpewBloodLevel = MercPtrs[ SWeaponHit.usSoldierID ]->pathing.bLevel; fCanSpewBlood = TRUE; // If on anything other than bLevel of 0, we can pretty much freely spew blood if ( bSpewBloodLevel == 0 ) { if ( gubWorldMovementCosts[ sNewGridNo ][ gOppositeDirection[ SWeaponHit.usDirection ] ][ 0 ] >= TRAVELCOST_BLOCKED ) { fCanSpewBlood = FALSE; } } else { // If a roof does not exist here, make level = 0 if ( !IsRoofPresentAtGridno( sNewGridNo ) ) { bSpewBloodLevel = 0; } } if ( fCanSpewBlood ) { // Drop blood dude! InternalDropBlood( sNewGridNo, bSpewBloodLevel, 0, (UINT8)(MAXBLOODQUANTITY), 1 ); } } return( fStopped ); } void BulletHitStructure( BULLET * pBullet, UINT16 usStructureID, INT32 iImpact, SOLDIERTYPE * pFirer, FIXEDPT qCurrX, FIXEDPT qCurrY, FIXEDPT qCurrZ, BOOLEAN fStopped ) { EV_S_STRUCTUREHIT SStructureHit; SStructureHit.sXPos = (INT16) FIXEDPT_TO_INT32( qCurrX + FloatToFixed( 0.5f ) ); // + 0.5); SStructureHit.sYPos = (INT16) FIXEDPT_TO_INT32( qCurrY + FloatToFixed( 0.5f ) ); // (dCurrY + 0.5); SStructureHit.sZPos = CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16) FIXEDPT_TO_INT32( qCurrZ + FloatToFixed( 0.5f )) );// dCurrZ + 0.5) ); SStructureHit.usWeaponIndex = pFirer->usAttackingWeapon; SStructureHit.bWeaponStatus = pBullet->ubItemStatus; SStructureHit.ubAttackerID = pFirer->ubID; SStructureHit.usStructureID = usStructureID; SStructureHit.iImpact = iImpact; SStructureHit.iBullet = pBullet->iBullet; if (is_networked) SStructureHit.fStopped = fStopped; //hayden if(is_client) send_hitstruct(&SStructureHit); StructureHit( SStructureHit.iBullet, SStructureHit.usWeaponIndex, SStructureHit.bWeaponStatus, SStructureHit.ubAttackerID, SStructureHit.sXPos, SStructureHit.sYPos, SStructureHit.sZPos, SStructureHit.usStructureID, SStructureHit.iImpact, fStopped ); } void BulletHitWindow( BULLET *pBullet, INT16 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth ) { if (is_networked) { EV_S_WINDOWHIT SWindowHit; SWindowHit.sGridNo = sGridNo; SWindowHit.usStructureID = usStructureID; SWindowHit.fBlowWindowSouth = fBlowWindowSouth; SWindowHit.fLargeForce = FALSE; SWindowHit.iBullet = pBullet->iBullet; SWindowHit.ubAttackerID=pBullet->pFirer->ubID; //hayden if(is_client) send_hitwindow(&SWindowHit); } WindowHit( sGridNo, usStructureID, fBlowWindowSouth, FALSE ); } void BulletMissed( BULLET *pBullet, SOLDIERTYPE * pFirer ) { if (is_networked) { EV_S_MISS SMiss; SMiss.iBullet=pBullet->iBullet; SMiss.ubAttackerID=pFirer->ubID; //hayden if(is_client) send_miss(&SMiss); } ShotMiss( pFirer->ubID, pBullet->iBullet ); } UINT32 ChanceOfBulletHittingStructure( INT32 iDistance, INT32 iDistanceToTarget, INT16 sHitBy ) { //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ChanceOfBulletHittingStructure")); INT32 iCloseToCoverPenalty; if (iDistance / CELL_X_SIZE > MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE) { return( MAX_CHANCE_OF_HITTING_STRUCTURE ); } else { iCloseToCoverPenalty = iDistance / 5 - (iDistanceToTarget - iDistance); if (iCloseToCoverPenalty < 0) { iCloseToCoverPenalty = 0; } if (sHitBy < 0) { // add 20% to distance so that misses hit nearer obstacles a bit more iDistance += iDistance / 5; } if ( ( (iDistance + iCloseToCoverPenalty) / CELL_X_SIZE ) > MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE ) { //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ChanceOfBulletHittingStructure done")); return( MAX_CHANCE_OF_HITTING_STRUCTURE ); } else { //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ChanceOfBulletHittingStructure done")); return( guiStructureHitChance[ (iDistance + iCloseToCoverPenalty) / CELL_X_SIZE ] ); } } } INT32 StructureResistanceIncreasedByRange( INT32 iImpactReduction, INT32 iGunRange, INT32 iDistance ) { return( iImpactReduction * ( 100 + PERCENT_BULLET_SLOWED_BY_RANGE * (iDistance - iGunRange) / iGunRange ) / 100 ); /* if ( iDistance > iGunRange ) { return( iImpactReduction * ( 100 + PERCENT_BULLET_SLOWED_BY_RANGE * (iDistance - iGunRange) / iGunRange ) / 100 ); } else { return( iImpactReduction ); } */ } INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN * pfHit ) { DOOR *pDoor; INT16 sLockDamage; // returns remaining impact amount INT32 iCurrImpact; INT32 iImpactReduction; *pfHit = FALSE; if (pBullet->usFlags & BULLET_FLAG_KNIFE || pBullet->usFlags & BULLET_FLAG_MISSILE || pBullet->usFlags & BULLET_FLAG_TANK_CANNON || pBullet->usFlags & BULLET_FLAG_FLAME ) { // stops! *pfHit = TRUE; return( 0 ); } //else if ( pBullet->usFlags & BULLET_FLAG_SMALL_MISSILE ) //{ // stops if using HE ammo else if ( AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].highExplosive && !AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].antiTank ) { *pfHit = TRUE; return( 0 ); } // } // ATE: Alrighty, check for shooting door locks... // First check this is a type of struct that can handle locks... if ( pStructure->fFlags & ( STRUCTURE_DOOR | STRUCTURE_OPENABLE ) && PythSpacesAway( (INT16) pBullet->sTargetGridNo, pStructure->sGridNo ) <= 2 ) { // lookup lock table to see if we have a lock, // and then remove lock if enough damage done.... pDoor = FindDoorInfoAtGridNo( pBullet->sGridNo ); // Does it have a lock? INT16 lockBustingPower = AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].lockBustingPower; // WANNE: bugfix: No door returned, so the game crashes! if (pDoor) { DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Door info: damage = %d, pick difficulty = %d, smash difficulty = %d, lockbuster power = %d",pDoor->bLockDamage,LockTable[ pDoor->ubLockID ].ubPickDifficulty,LockTable[ pDoor->ubLockID ].ubSmashDifficulty,lockBustingPower) ); if ( pDoor && (( LockTable[ pDoor->ubLockID ].ubPickDifficulty < 50 && LockTable[ pDoor->ubLockID ].ubSmashDifficulty < 70 ) || lockBustingPower*2 >= LockTable[ pDoor->ubLockID ].ubSmashDifficulty ) ) { // Yup..... // Chance that it hit the lock.... if ( PreRandom( 2 ) == 0 || lockBustingPower > 0 ) { // Adjust damage-- CC adjust this based on gun type, etc..... //sLockDamage = (INT16)( 35 + Random( 35 ) ); sLockDamage = (INT16) (pBullet->iImpact - pBullet->iImpactReduction ); sLockDamage += (INT16) PreRandom( sLockDamage ); sLockDamage += lockBustingPower; sLockDamage = min(sLockDamage,127); ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, TacticalStr[ LOCK_HAS_BEEN_HIT ] ); //Madd: catch the overflow if ( sLockDamage + pDoor->bLockDamage > 127 ) pDoor->bLockDamage = 127; else pDoor->bLockDamage+= sLockDamage; // Check if it has been shot! if ( pDoor->bLockDamage > LockTable[ pDoor->ubLockID ].ubSmashDifficulty || sLockDamage > LockTable[ pDoor->ubLockID ].ubSmashDifficulty ) { // Display message! ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, TacticalStr[ LOCK_HAS_BEEN_DESTROYED ] ); // succeeded! door can never be locked again, so remove from door list... RemoveDoorInfoFromTable( pDoor->sGridNo ); // MARKSMANSHIP GAIN (marksPts): Opened/Damaged a door StatChange( pBullet->pFirer, MARKAMT, 10, FALSE ); } } } } } // okay, this seems pretty weird, so here's the comment to explain it: // iImpactReduction is the reduction in impact due to the structure // pBullet->iImpactReduction is the accumulated reduction in impact // for all bullets encountered thus far // iCurrImpact is the original impact value of the bullet reduced due to // range. To avoid problems involving multiple multiplication // ( (1 - X) * (1 - Y) != (1 - X - Y) ! ), this is calculated from // scratch at each collision with an obstacle // reduction due to range is 25% per "max range" if (PreRandom( 100 ) < pStructure->pDBStructureRef->pDBStructure->ubDensity) { iCurrImpact = pBullet->iImpact; //iCurrImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange ); iImpactReduction = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ]; iImpactReduction = StructureResistanceIncreasedByRange( iImpactReduction, pBullet->iRange, pBullet->iLoop ); iImpactReduction = (INT32) (iImpactReduction * AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionMultiplier / max(1,AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionDivisor)); //switch (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType) //{ // case AMMO_HP: // iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_HP( iImpactReduction ); // break; // case AMMO_AP: // case AMMO_HEAT: // iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_AP( iImpactReduction ); // break; // case AMMO_SUPER_AP: // iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_SAP( iImpactReduction ); // break; // default: // break; //} pBullet->iImpactReduction += iImpactReduction; // really weak stuff like grass should never *stop* a bullet, maybe slow it though if ( pStructure->pDBStructureRef->pDBStructure->ubArmour == MATERIAL_LIGHT_VEGETATION ) { // just return a +ve value to indicate the bullet wasn't stopped *pfHit = FALSE; return( 1 ); } *pfHit = TRUE; return( iCurrImpact - pBullet->iImpactReduction ); } else { // just return a +ve value to indicate the bullet wasn't stopped *pfHit = FALSE; return( 1 ); } } INT32 CTGTHandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure ) { // returns reduction in impact for summing in CTGT INT32 iCurrImpact; INT32 iImpactReduction; if (pBullet->usFlags & BULLET_FLAG_KNIFE || pBullet->usFlags & BULLET_FLAG_MISSILE || pBullet->usFlags & BULLET_FLAG_FLAME || pBullet->usFlags & BULLET_FLAG_TANK_CANNON ) { // knife/rocket stops when it hits anything, and people block completely return( pBullet->iImpact ); } //else if ( pBullet->usFlags & BULLET_FLAG_SMALL_MISSILE ) //{ // stops if using HE ammo else if ( AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].highExplosive && !AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].antiTank ) { return( pBullet->iImpact ); } // } else if ( pStructure->fFlags & STRUCTURE_PERSON ) { if ( pStructure->usStructureID != pBullet->ubFirerID && pStructure->usStructureID != pBullet->ubTargetID ) { } } // okay, this seems pretty weird, so here's the comment to explain it: // iImpactReduction is the reduction in impact due to the structure // pBullet->iImpactReduction is the accumulated reduction in impact // for all bullets encountered thus far // iCurrImpact is the original impact value of the bullet reduced due to // range. To avoid problems involving multiple multiplication // ( (1 - X) * (1 - Y) != (1 - X - Y) ! ), this is calculated from // scratch at each collision with an obstacle // reduction due to range is 25% per "max range" //iCurrImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange ); iCurrImpact = pBullet->iImpact; // multiply impact reduction by 100 to retain fractions for a bit... iImpactReduction = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ] * pStructure->pDBStructureRef->pDBStructure->ubDensity / 100; iImpactReduction = StructureResistanceIncreasedByRange( iImpactReduction, pBullet->iRange, pBullet->iLoop ); iImpactReduction = (INT32)(iImpactReduction * AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionMultiplier / max(1,AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionDivisor)); //switch (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType) //{ // case AMMO_HP: // iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_HP( iImpactReduction ); // break; // case AMMO_AP: // iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_AP( iImpactReduction ); // break; // case AMMO_SUPER_AP: // iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_SAP( iImpactReduction ); // break; // default: // break; //} return( iImpactReduction ); } UINT8 CalcChanceToGetThrough( BULLET * pBullet ) { FIXEDPT qLandHeight; INT32 iCurrAboveLevelZ; INT32 iCurrCubesAboveLevelZ; INT16 sDesiredLevel; INT32 iOldTileX; INT32 iOldTileY; INT32 iOldCubesZ; MAP_ELEMENT * pMapElement; STRUCTURE * pStructure; STRUCTURE * pRoofStructure = NULL; FIXEDPT qLastZ; BOOLEAN fIntended; INT8 bOldLOSIndexX; INT8 bOldLOSIndexY; INT32 iChanceToGetThrough = 100; FIXEDPT qDistToTravelX; FIXEDPT qDistToTravelY; INT32 iStepsToTravelX; INT32 iStepsToTravelY; INT32 iStepsToTravel; INT32 iNumLocalStructures; INT32 iStructureLoop; UINT32 uiChanceOfHit; INT32 iGridNo; INT32 iTotalStructureImpact; BOOLEAN fResolveHit; FIXEDPT qWallHeight; FIXEDPT qWindowBottomHeight; FIXEDPT qWindowTopHeight; DebugLOS( "Starting CalcChanceToGetThrough" ); do { // check a particular tile // retrieve values from world for this particular tile iGridNo = pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS; DebugLOS( String( "CTGT now at %ld", iGridNo ) ); pMapElement = &(gpWorldLevelData[ iGridNo ] ); qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) ); qWallHeight = gqStandardWallHeight + qLandHeight; qWindowBottomHeight = gqStandardWindowBottomHeight + qLandHeight; qWindowTopHeight = gqStandardWindowTopHeight + qLandHeight; // Assemble list of structures we might hit! iNumLocalStructures = 0; pStructure = pMapElement->pStructureHead; // calculate chance of hitting each structure uiChanceOfHit = ChanceOfBulletHittingStructure( pBullet->iLoop, pBullet->iDistanceLimit, 0 ); // reset roof structure pointer each tile pRoofStructure = NULL; if (iGridNo == (INT32) pBullet->sTargetGridNo) { fIntended = TRUE; // if in the same tile as our destination, we WANT to hit the structure! uiChanceOfHit = 100; } else { fIntended = FALSE; } iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight ); if (iCurrAboveLevelZ < 0) { // ground is in the way! DebugLOS(String("CalcChanceToGetThrough done ground in way #1")); return( 0 ); } iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ ); DebugLOS(String("CalcChanceToGetThrough: while pStructure")); while( pStructure ) { if (pStructure->fFlags & ALWAYS_CONSIDER_HIT) { // ALWAYS add walls gpLocalStructure[iNumLocalStructures] = pStructure; // fence is special //(iCurrCubesAboveLevelZ <= iStartCubesAboveLevelZ) if ( pStructure->fFlags & STRUCTURE_ANYFENCE ) { if (pStructure->pDBStructureRef->pDBStructure->ubDensity < 100) { guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit; } else if ( (pBullet->iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bStartCubesAboveLevelZ) && (pBullet->bEndCubesAboveLevelZ >= iCurrCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) ) { guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit; } else if ( (pBullet->iLoop + pBullet->iDistanceLimit / 50 >= pBullet->iDistanceLimit - CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bEndCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) ) { guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit; } else { guiLocalStructureCTH[iNumLocalStructures] = 100; } } else { guiLocalStructureCTH[iNumLocalStructures] = 100; } gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } else if (pStructure->fFlags & STRUCTURE_ROOF) { // only consider roofs if the flag is set; don't add them to the array since they // are a special case if (pBullet->fCheckForRoof) { pRoofStructure = pStructure; if ( pRoofStructure ) { qLastZ = pBullet->qCurrZ - pBullet->qIncrZ; // if just on going to next tile we cross boundary, then roof stops bullet here! if ( (qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight)) { // hit a roof DebugLOS(String("ChanceChanceToGetThrough done hit roof #1" )); return( 0 ); } } } } else if (pStructure->fFlags & STRUCTURE_PERSON) { if ( (pStructure->usStructureID != pBullet->ubFirerID) && (pStructure->usStructureID != pBullet->ubTargetID ) ) { // ignore intended target since we will get closure upon reaching the center // of the destination tile // ignore intervening target if not visible; PCs are always visible so AI will never skip them on that // basis if ( !fIntended && (MercPtrs[ pStructure->usStructureID ]->bVisible == TRUE) ) { // in actually moving the bullet, we consider only count friends as targets if the bullet is unaimed // (buckshot), if they are the intended target, or beyond the range of automatic friendly fire hits // OR a 1 in 30 chance occurs if (gAnimControl[ MercPtrs[pStructure->usStructureID]->usAnimState ].ubEndHeight == ANIM_STAND && ( (pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS) || (!pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS_UNAIMED) ) ) { // could hit this person! gpLocalStructure[iNumLocalStructures] = pStructure; // CJC commented this out because of tank trying to shoot through another tank //guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit; guiLocalStructureCTH[iNumLocalStructures] = 100; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } else { // minimal chance of hitting this person gpLocalStructure[iNumLocalStructures] = pStructure; guiLocalStructureCTH[iNumLocalStructures] = MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } } } } else if ( pStructure->fFlags & STRUCTURE_CORPSE ) { if ( iGridNo == (INT32) pBullet->sTargetGridNo || (pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles >= 10) ) { // could hit this corpse! // but we should ignore the corpse if there is someone standing there if ( FindStructure( (INT16) iGridNo, STRUCTURE_PERSON ) == NULL ) { gpLocalStructure[iNumLocalStructures] = pStructure; iNumLocalStructures++; } } } else { if ( pBullet->iLoop > CLOSE_TO_FIRER && !fIntended ) { // could hit it gpLocalStructure[iNumLocalStructures] = pStructure; guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } } pStructure = pStructure->pNext; } // record old tile location for loop purposes iOldTileX = pBullet->iCurrTileX; iOldTileY = pBullet->iCurrTileY; DebugLOS(String("CalcChanceToGetThrough: checking for location within the tile")); do { // check a particular location within the tile // check for collision with the ground iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight ); if (iCurrAboveLevelZ < 0) { // ground is in the way! DebugLOS(String("ChanceOfBulletHittingStructure done ground in way #2")); return( 0 ); } // check for the existence of structures pStructure = pMapElement->pStructureHead; if (pStructure == NULL) { // no structures in this tile, and THAT INCLUDES ROOFS AND PEOPLE! :-) // new system; figure out how many steps until we cross the next edge // and then fast forward that many steps. iOldTileX = pBullet->iCurrTileX; iOldTileY = pBullet->iCurrTileY; iOldCubesZ = pBullet->iCurrCubesZ; if (pBullet->qIncrX > 0) { qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE )); iStepsToTravelX = qDistToTravelX / pBullet->qIncrX; } else if (pBullet->qIncrX < 0) { qDistToTravelX = pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ); iStepsToTravelX = qDistToTravelX / (-pBullet->qIncrX); } else { // make sure we don't consider X a limit :-) iStepsToTravelX = 1000000; } if (pBullet->qIncrY > 0) { qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE )); iStepsToTravelY = qDistToTravelY / pBullet->qIncrY; } else if (pBullet->qIncrY < 0) { qDistToTravelY = pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ); iStepsToTravelY = qDistToTravelY / (-pBullet->qIncrY); } else { // make sure we don't consider Y a limit :-) iStepsToTravelY = 1000000; } // add 1 to the # of steps to travel to go INTO the next tile iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1; pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel; pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel; pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel; pBullet->iLoop += iStepsToTravel; // check for ground collision if (pBullet->qCurrZ < qLandHeight && pBullet->iLoop < pBullet->iDistanceLimit) { // ground is in the way! DebugLOS(String("ChanceChanceToGetThrough done ground in way #3" )); return( 0 ); } // figure out the new tile location pBullet->iCurrTileX = FIXEDPT_TO_TILE_NUM( pBullet->qCurrX ); pBullet->iCurrTileY = FIXEDPT_TO_TILE_NUM( pBullet->qCurrY ); pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) ); pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX ); pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY ); DebugLOS( String( " CTGT at %ld %ld after traversing empty tile", pBullet->bLOSIndexX, pBullet->bLOSIndexY ) ); } else { // there are structures in this tile iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ ); // figure out the LOS cube level of the current point if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX) { if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX) { // check objects on the ground sDesiredLevel = STRUCTURE_ON_GROUND; } else { // check objects on roofs sDesiredLevel = STRUCTURE_ON_ROOF; iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF; } // check structures for collision for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++) { pStructure = gpLocalStructure[iStructureLoop]; if (pStructure && pStructure->sCubeOffset == sDesiredLevel) { if (((*(pStructure->pShape))[pBullet->bLOSIndexX][pBullet->bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0) { if (pStructure->fFlags & STRUCTURE_PERSON) { // hit someone? if (fIntended) { // gotcha! ... return chance to get through iChanceToGetThrough = iChanceToGetThrough * (pBullet->iImpact - pBullet->iImpactReduction) / pBullet->iImpact; DebugLOS(String("ChanceChanceToGetThrough done, hit intended, returning %d", iChanceToGetThrough )); return( (UINT8) iChanceToGetThrough ); } else { gubLocalStructureNumTimesHit[iStructureLoop]++; } } else if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && pBullet->qCurrZ >= qWindowBottomHeight && pBullet->qCurrZ <= qWindowTopHeight) { fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle ); if (fResolveHit) { // the bullet would keep on going! unless we're considering a knife... if (pBullet->usFlags & BULLET_FLAG_KNIFE) { gubLocalStructureNumTimesHit[iStructureLoop]++; } } } else if (pBullet->iLoop > CLOSE_TO_FIRER || (pStructure->fFlags & ALWAYS_CONSIDER_HIT)) { if (pStructure->fFlags & STRUCTURE_WALLSTUFF) { // possibly shooting at corner in which case we should let it pass fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle ); } else { fResolveHit = TRUE; } if (fResolveHit) { gubLocalStructureNumTimesHit[iStructureLoop]++; } } } } } } // got past everything; go to next LOS location within // tile, horizontally or vertically bOldLOSIndexX = pBullet->bLOSIndexX; bOldLOSIndexY = pBullet->bLOSIndexY; iOldCubesZ = pBullet->iCurrCubesZ; do { pBullet->qCurrX += pBullet->qIncrX; pBullet->qCurrY += pBullet->qIncrY; if (pRoofStructure) { qLastZ = pBullet->qCurrZ; pBullet->qCurrZ += pBullet->qIncrZ; if ( (qLastZ > qWallHeight && pBullet->qCurrZ < qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ > qWallHeight)) { // hit roof! //pBullet->iImpactReduction += CTGTHandleBulletStructureInteraction( pBullet, pRoofStructure ); //if (pBullet->iImpactReduction >= pBullet->iImpact) { DebugLOS(String("ChanceChanceToGetThrough done hit roof #2" )); return( 0 ); } } } else { pBullet->qCurrZ += pBullet->qIncrZ; } pBullet->iLoop++; pBullet->bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrX ) % CELL_X_SIZE ); pBullet->bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrY ) % CELL_Y_SIZE ); pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) ); } while( (pBullet->bLOSIndexX == bOldLOSIndexX) && (pBullet->bLOSIndexY == bOldLOSIndexY) && (pBullet->iCurrCubesZ == iOldCubesZ)); DebugLOS( String( " CTGT at %ld %ld %ld after moving in nonempty tile from %ld %ld %ld", pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->iCurrCubesZ, bOldLOSIndexX, bOldLOSIndexY, iOldCubesZ ) ); pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE; pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE; } } while( (pBullet->iLoop < pBullet->iDistanceLimit) && (pBullet->iCurrTileX == iOldTileX) && (pBullet->iCurrTileY == iOldTileY)); if ( pBullet->iCurrTileX < 0 || pBullet->iCurrTileX >= WORLD_COLS || pBullet->iCurrTileY < 0 || pBullet->iCurrTileY >= WORLD_ROWS ) { DebugLOS(String("CalcChanceToGetThrough done invalid position")); return( 0 ); } DebugLOS(String("CalcChanceToGetThrough check for bullet drop")); pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY , pBullet->iCurrTileX ); if (pBullet->iLoop > pBullet->iRange * 2) { // beyond max effective range, bullet starts to drop! // since we're doing an increment based on distance, not time, the // decrement is scaled down depending on how fast the bullet is (effective range) if ( pBullet->iRange <= 0 ) { pBullet->iRange = 1; } pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2); } // end of the tile... DebugLOS(String("CalcChanceToGetThrough end of tile")); if (iNumLocalStructures > 0) { for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++) { // Calculate the total impact based on the number of points in the structure that were hit if (gubLocalStructureNumTimesHit[iStructureLoop] > 0) { iTotalStructureImpact = CTGTHandleBulletStructureInteraction( pBullet, gpLocalStructure[iStructureLoop] ) * gubLocalStructureNumTimesHit[iStructureLoop]; // reduce the impact reduction of a structure tile to that of the bullet, since it can't do MORE than stop it. iTotalStructureImpact = __min( iTotalStructureImpact, pBullet->iImpact ); // add to "impact reduction" based on strength of structure weighted by probability of hitting it pBullet->iImpactReduction += (iTotalStructureImpact * guiLocalStructureCTH[iStructureLoop]) / 100; } } if (pBullet->iImpactReduction >= pBullet->iImpact) { DebugLOS(String("CalcChanceToGetThrough done impact reduced")); return( 0 ); } } } while( pBullet->iLoop < pBullet->iDistanceLimit ); // unless the distance is integral, after the loop there will be a // fractional amount of distance remaining which is unchecked // but we shouldn't(?) need to check it because the target is there! DebugLOS(String("CalcChanceToGetThrough simple calc, %d * (%d - %d) / %d", iChanceToGetThrough, pBullet->iImpact, pBullet->iImpactReduction, (max(pBullet->iImpact,1)))); // try simple chance to get through, ignoring range effects iChanceToGetThrough = iChanceToGetThrough * (pBullet->iImpact - pBullet->iImpactReduction) / (max(pBullet->iImpact,1)); if (iChanceToGetThrough < 0) { iChanceToGetThrough = 0; } DebugLOS(String("CalcChanceToGetThrough done, returning %d", iChanceToGetThrough)); return( (UINT8) iChanceToGetThrough ); } UINT8 SoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier ) { FLOAT dEndZPos; BOOLEAN fOk; if (pStartSoldier == pEndSoldier) { return( 0 ); } CHECKF( pStartSoldier ); CHECKF( pEndSoldier ); fOk = CalculateSoldierZPos( pEndSoldier, TARGET_POS, &dEndZPos ); if (!fOk) { return( FALSE ); } // set startsoldier's target ID ... need an ID stored in case this // is the AI calculating cover to a location where he might not be any more pStartSoldier->ubCTGTTargetID = pEndSoldier->ubID; return( ChanceToGetThrough( pStartSoldier, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos ) ); } UINT8 SoldierToSoldierBodyPartChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, UINT8 ubAimLocation ) { // does like StS-CTGT but with a particular body part in mind FLOAT dEndZPos; BOOLEAN fOk; UINT8 ubPosType; if (pStartSoldier == pEndSoldier) { return( 0 ); } CHECKF( pStartSoldier ); CHECKF( pEndSoldier ); switch( ubAimLocation ) { case AIM_SHOT_HEAD: ubPosType = HEAD_TARGET_POS; break; case AIM_SHOT_TORSO: ubPosType = TORSO_TARGET_POS; break; case AIM_SHOT_LEGS: ubPosType = LEGS_TARGET_POS; break; default: ubPosType = TARGET_POS; break; } fOk = CalculateSoldierZPos( pEndSoldier, ubPosType, &dEndZPos ); if (!fOk) { return( FALSE ); } // set startsoldier's target ID ... need an ID stored in case this // is the AI calculating cover to a location where he might not be any more pStartSoldier->ubCTGTTargetID = pEndSoldier->ubID; return( ChanceToGetThrough( pStartSoldier, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos ) ); } UINT8 SoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bCubeLevel, UINT8 ubTargetID ) { FLOAT dEndZPos; INT16 sXPos; INT16 sYPos; INT8 bStructHeight; SOLDIERTYPE * pEndSoldier; if (pStartSoldier->sGridNo == sGridNo) { return( 0 ); } CHECKF( pStartSoldier ); pEndSoldier = SimpleFindSoldier( sGridNo, bLevel ); if (pEndSoldier != NULL) { return( SoldierToSoldierChanceToGetThrough( pStartSoldier, pEndSoldier ) ); } else { if (bCubeLevel) { // fire at the centre of the cube specified dEndZPos = ( ( (FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE) ) - 0.5f) * HEIGHT_UNITS_PER_INDEX; } else { bStructHeight = GetStructureTargetHeight( sGridNo, (BOOLEAN) (bLevel == 1) ); if (bStructHeight > 0) { // fire at the centre of the cube of the tallest structure dEndZPos = ((FLOAT) (bStructHeight + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX; } else { // fire at 1 unit above the level of the ground dEndZPos = (FLOAT) ((bLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1); } } dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight ); ConvertGridNoToXY( sGridNo, &sXPos, &sYPos ); sXPos = sXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sYPos = sYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); // set startsoldier's target ID ... need an ID stored in case this // is the AI calculating cover to a location where he might not be any more pStartSoldier->ubCTGTTargetID = ubTargetID; return( ChanceToGetThrough( pStartSoldier, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos ) ); } } UINT8 AISoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier ) { // Like a standard CTGT algorithm BUT fakes the start soldier at standing height FLOAT dEndZPos; BOOLEAN fOk; UINT8 ubChance; UINT16 usTrueState; if (pStartSoldier == pEndSoldier) { return( 0 ); } CHECKF( pStartSoldier ); CHECKF( pEndSoldier ); fOk = CalculateSoldierZPos( pEndSoldier, TARGET_POS, &dEndZPos ); if (!fOk) { return( FALSE ); } usTrueState = pStartSoldier->usAnimState; pStartSoldier->usAnimState = STANDING; // set startsoldier's target ID ... need an ID stored in case this // is the AI calculating cover to a location where he might not be any more pStartSoldier->ubCTGTTargetID = NOBODY; ubChance = ChanceToGetThrough( pStartSoldier, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos ); pStartSoldier->usAnimState = usTrueState; return( ubChance ); } UINT8 AISoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bCubeLevel ) { FLOAT dEndZPos; INT16 sXPos; INT16 sYPos; INT8 bStructHeight; SOLDIERTYPE * pEndSoldier; UINT16 usTrueState; UINT8 ubChance; if (pStartSoldier->sGridNo == sGridNo) { return( 0 ); } CHECKF( pStartSoldier ); pEndSoldier = SimpleFindSoldier( sGridNo, bLevel ); if (pEndSoldier != NULL) { return( AISoldierToSoldierChanceToGetThrough( pStartSoldier, pEndSoldier ) ); } else { if (bCubeLevel) { // fire at the centre of the cube specified dEndZPos = ( (FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX; } else { bStructHeight = GetStructureTargetHeight( sGridNo, (BOOLEAN) (bLevel == 1) ); if (bStructHeight > 0) { // fire at the centre of the cube of the tallest structure dEndZPos = ((FLOAT) (bStructHeight + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX; } else { // fire at 1 unit above the level of the ground dEndZPos = (FLOAT) ((bLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1); } } dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight ); ConvertGridNoToXY( sGridNo, &sXPos, &sYPos ); sXPos = sXPos * CELL_X_SIZE + (CELL_X_SIZE / 2); sYPos = sYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2); // set startsoldier's target ID ... need an ID stored in case this // is the AI calculating cover to a location where he might not be any more pStartSoldier->ubCTGTTargetID = NOBODY; usTrueState = pStartSoldier->usAnimState; pStartSoldier->usAnimState = STANDING; ubChance = ChanceToGetThrough( pStartSoldier, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos ); pStartSoldier->usAnimState = usTrueState; return( ubChance ); } } void CalculateFiringIncrements( DOUBLE ddHorizAngle, DOUBLE ddVerticAngle, DOUBLE dd2DDistance, BULLET * pBullet, DOUBLE * pddNewHorizAngle, DOUBLE * pddNewVerticAngle ) { INT32 iMissedBy = - pBullet->sHitBy; DOUBLE ddVerticPercentOfMiss; DOUBLE ddAbsVerticAngle; DOUBLE ddScrewupAdjustmentLimit; UINT32 uiChanceOfMissAbove; DOUBLE ddMinimumMiss; DOUBLE ddMaximumMiss; DOUBLE ddAmountOfMiss; if (iMissedBy > 0) { ddVerticPercentOfMiss = PreRandom( 50 ); ddAbsVerticAngle = ddVerticAngle; if (ddAbsVerticAngle < 0) { ddAbsVerticAngle *= -1.0; } // chance of shooting over target is 60 for horizontal shots, up to 80% for shots at 22.5 degrees, // and then down again to 50% for shots at 45+%. if (ddAbsVerticAngle < DEGREES_22_5) { uiChanceOfMissAbove = 60 + (INT32) (20 * (ddAbsVerticAngle) / DEGREES_22_5); } else if (ddAbsVerticAngle < DEGREES_45) { uiChanceOfMissAbove = 80 - (INT32) (30.0 * (ddAbsVerticAngle - DEGREES_22_5) / DEGREES_22_5); } else { uiChanceOfMissAbove = 50; } // figure out change in horizontal and vertical angle due to shooter screwup // the more the screwup, the greater the angle; // for the look of things, the further away, reduce the angle a bit. ddScrewupAdjustmentLimit = (dd2DDistance / CELL_X_SIZE) / 200; if (ddScrewupAdjustmentLimit > MAX_AIMING_SCREWUP / 2) { ddScrewupAdjustmentLimit = MAX_AIMING_SCREWUP / 2; } ddMaximumMiss = MAX_AIMING_SCREWUP - ddScrewupAdjustmentLimit; // Want to make sure that not too many misses actually hit the target after all // to miss a target at 1 tile is about 30 degrees off, at 5 tiles, 6 degrees off // at 15 tiles, 2 degrees off. Thus 30 degrees divided by the # of tiles distance. ddMinimumMiss = MIN_AIMING_SCREWUP / (dd2DDistance / CELL_X_SIZE ); if (ddMinimumMiss > ddMaximumMiss) { ddMinimumMiss = ddMaximumMiss; } ddAmountOfMiss = ( (ddMaximumMiss - ddMinimumMiss) * (DOUBLE) iMissedBy) / 100.0 + ddMinimumMiss; // miss to the left or right if (PreRandom( 2 ) ) { ddHorizAngle += ddAmountOfMiss * (100.0 - ddVerticPercentOfMiss) / 100.0; } else { ddHorizAngle -= ddAmountOfMiss * (100.0 - ddVerticPercentOfMiss) / 100.0; } // miss up or down if (PreRandom( 100 ) < uiChanceOfMissAbove) { ddVerticAngle += ddAmountOfMiss * ddVerticPercentOfMiss / 100.0; } else { ddVerticAngle -= ddAmountOfMiss * ddVerticPercentOfMiss / 100.0; } } *pddNewHorizAngle = ddHorizAngle; *pddNewVerticAngle = ddVerticAngle; pBullet->qIncrX = FloatToFixed( (FLOAT) cos( ddHorizAngle ) ); pBullet->qIncrY = FloatToFixed( (FLOAT) sin( ddHorizAngle ) ); // this is the same as multiplying the X and Y increments by the projection of the line in // 3-space onto the horizontal plane, without reducing the X/Y increments and thus slowing // the LOS code pBullet->qIncrZ = FloatToFixed( (FLOAT) ( sin( ddVerticAngle ) / sin( (PI/2) - ddVerticAngle ) * 2.56 ) ); } //zilpin: pellet spread patterns externalized in XML //New firing increments calculation, without any adjustment. void CalculateFiringIncrementsSimple( DOUBLE ddHorizAngle, DOUBLE ddVerticAngle, BULLET * pBullet) { pBullet->qIncrX = FloatToFixed( (FLOAT) cos( ddHorizAngle ) ); pBullet->qIncrY = FloatToFixed( (FLOAT) sin( ddHorizAngle ) ); pBullet->qIncrZ = FloatToFixed( (FLOAT) ( sin( ddVerticAngle ) / sin( (PI/2) - ddVerticAngle ) * 2.56 ) ); } INT8 FireBullet( SOLDIERTYPE * pFirer, BULLET * pBullet, BOOLEAN fFake ) { //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet")); pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE; pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE; pBullet->bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrX ) % CELL_X_SIZE ); pBullet->bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrY ) % CELL_Y_SIZE ); pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) ); pBullet->iLoop = 1; pBullet->pFirer = pFirer; pBullet->iImpactReduction = 0; pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY, pBullet->iCurrTileX ); //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet: bullet info set")); if (fFake) { pBullet->ubTargetID = pFirer->ubCTGTTargetID; return( CalcChanceToGetThrough( pBullet ) ); } else { pBullet->ubTargetID = pFirer->ubTargetID; //if ( gGameSettings.fOptions[ TOPTION_HIDE_BULLETS ] ) //{ // pBullet->uiLastUpdate = 0; // pBullet->ubTilesPerUpdate = 4; //} //else //{ pBullet->uiLastUpdate = 0; pBullet->ubTilesPerUpdate = 1; //} // increment shots fired if shooter has a merc profile if( ( pFirer->ubProfile != NO_PROFILE ) && ( pFirer->bTeam == 0 ) ) { // another shot fired gMercProfiles[ pFirer->ubProfile ].usShotsFired++; } if ( Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE ) { pBullet->usClockTicksPerUpdate = 30; } else { //afp-start //always a fast bullet if ( pBullet->usFlags & ( BULLET_FLAG_CREATURE_SPIT | BULLET_FLAG_KNIFE | BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME /*| BULLET_FLAG_TRACER*/ ) ) pBullet->usClockTicksPerUpdate = (Weapon[ pFirer->usAttackingWeapon ].ubBulletSpeed + GetBulletSpeedBonus(&pFirer->inv[pFirer->ubAttackingHand]) ) / 10; else if (gGameExternalOptions.gbBulletTracer) pBullet->usClockTicksPerUpdate = (Weapon[ pFirer->usAttackingWeapon ].ubBulletSpeed + GetBulletSpeedBonus(&pFirer->inv[pFirer->ubAttackingHand]) ) / 10; else pBullet->usClockTicksPerUpdate = 1; //afp-end } //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet: handle flags")); HandleBulletSpecialFlags( pBullet->iBullet ); //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet: move bullet")); MoveBullet( pBullet->iBullet ); //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet done")); return( TRUE ); } } /* DOUBLE CalculateVerticalAngle( SOLDIERTYPE * pFirer, SOLDIERTYPE * pTarget ) { DOUBLE dStartZ, dEndZ; CalculateSoldierZPos( pFirer, FIRING_POS, &dStartZ ); CalculateSoldierZPos( pTarget, TARGET_POS, &dEndZ ); dDeltaX = (FLOAT) CenterX( pTarget->sGridNo ) - (FLOAT) CenterX( pFirer->sGridNo ); dDeltaY = (FLOAT) CenterY( pTarget->sGridNo ) - (FLOAT) CenterY( pFirer->sGridNo ); dDeltaZ = dEndZ - dStartZ; d2DDistance = Distance2D( dDeltaX, dDeltaY ); ddOrigHorizAngle = atan2( dDeltaY, dDeltaX ); } */ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, UINT16 usHandItem, INT16 sHitBy, BOOLEAN fBuckshot, BOOLEAN fFake ) { //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,"FireBulletGivenTarget"); // fFake indicates that we should set things up for a call to ChanceToGetThrough FLOAT dStartZ=0; FLOAT d2DDistance=0; FLOAT dDeltaX=0; FLOAT dDeltaY=0; FLOAT dDeltaZ=0; FLOAT dStartX=0; FLOAT dStartY=0; DOUBLE ddOrigHorizAngle=0; DOUBLE ddOrigVerticAngle=0; DOUBLE ddHorizAngle=0; DOUBLE ddVerticAngle=0; DOUBLE ddAdjustedHorizAngle=0; DOUBLE ddAdjustedVerticAngle=0; DOUBLE ddDummyHorizAngle=0; DOUBLE ddDummyVerticAngle=0; BULLET * pBullet=NULL; INT32 iBullet=0; INT32 iDistance=0; UINT8 ubLoop=0; UINT8 ubShots=0; UINT8 ubImpact=0; INT8 bCTGT; UINT8 ubSpreadIndex = 0; UINT16 usBulletFlags = 0; int n=0; //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBulletGivenTarget")); CalculateSoldierZPos( pFirer, FIRING_POS, &dStartZ ); dStartX = (FLOAT) CenterX( pFirer->sGridNo ); dStartY = (FLOAT) CenterY( pFirer->sGridNo ); dDeltaX = dEndX - dStartX; dDeltaY = dEndY - dStartY; dDeltaZ = dEndZ - dStartZ; //lal bugfix if( dDeltaZ > 0 ) d2DDistance = Distance3D( dDeltaX, dDeltaY, dDeltaZ ); else d2DDistance = Distance2D( dDeltaX, dDeltaY ); //d2DDistance = Distance2D( dDeltaX, dDeltaY ); iDistance = (INT32) d2DDistance; if ( d2DDistance != iDistance ) { iDistance += 1; d2DDistance = (FLOAT) ( iDistance); } ddOrigHorizAngle = atan2( dDeltaY, dDeltaX ); ddOrigVerticAngle = atan2( dDeltaZ, (d2DDistance * 2.56f) ); ddAdjustedHorizAngle = ddOrigHorizAngle; ddAdjustedVerticAngle = ddOrigVerticAngle; ubShots = 1; fTracer = FALSE; // Check if we have spit as a weapon! if ( Weapon[ usHandItem ].ubWeaponClass == MONSTERCLASS ) { usBulletFlags |= BULLET_FLAG_CREATURE_SPIT; } else if ( Item[ usHandItem ].usItemClass == IC_THROWING_KNIFE ) { usBulletFlags |= BULLET_FLAG_KNIFE; } else if ( Item[usHandItem].rocketlauncher ) { usBulletFlags |= BULLET_FLAG_MISSILE; } else if ( Item[usHandItem].cannon ) { usBulletFlags |= BULLET_FLAG_TANK_CANNON; } else if ( Item[usHandItem].rocketrifle ) { usBulletFlags |= BULLET_FLAG_SMALL_MISSILE; } else if ( usHandItem == FLAMETHROWER ) { usBulletFlags |= BULLET_FLAG_FLAME; ubSpreadIndex = 2; } // HEADROCK HAM B2.5: Set tracer effect on/off for individual bullets in a Tracer Magazine, as part of the // New Tracer System. else if (gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) && AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect ) { UINT16 iBulletsLeft, iBulletsPerTracer; iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer; iBulletsLeft = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst; if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1) { fTracer = TRUE; } else { fTracer = FALSE; } } else if ( AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect && (pFirer->bDoBurst || gGameSettings.fOptions[ TOPTION_TRACERS_FOR_SINGLE_FIRE ]) ) { //usBulletFlags |= BULLET_FLAG_TRACER; fTracer = TRUE; } ubImpact =(UINT8) GetDamage(&pFirer->inv[pFirer->ubAttackingHand]); //zilpin: pellet spread patterns externalized in XML /* zilpin: The section below, including line comments, is the original adjustment made to multiple projectile stats. Left in comments for reference, but the new handling is after it. The fBuckshot flag is now ignored, so it can probably be taken out of the function definition. // if (!fFake) { if (fBuckshot) { // shotgun pellets fire 9 bullets doing 1/4 damage each if (!fFake) { ubShots = AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].numberOfBullets; // but you can't really aim the damn things very well! if (sHitBy > 0) { sHitBy = sHitBy / 2; } if ( IsDuckbill( &(pFirer->inv[pFirer->ubAttackingHand])) ) { ubSpreadIndex = 1; } if (pFirer->ubTargetID != NOBODY) { MercPtrs[ pFirer->ubTargetID ]->bNumPelletsHitBy = 0; } usBulletFlags |= BULLET_FLAG_BUCKSHOT; } // ubImpact = AMMO_DAMAGE_ADJUSTMENT_BUCKSHOT( ubImpact ); ubImpact = (UINT8) (ubImpact * AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].multipleBulletDamageMultiplier / max(1,AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].multipleBulletDamageDivisor) ); } } */ //zilpin: Begin new code block for spread patterns, number of projectiles, impact adjustment, etc. { ObjectData *weapon = &(pFirer->inv[pFirer->ubAttackingHand][0]->data); ubShots = AmmoTypes[ weapon->gun.ubGunAmmoType].numberOfBullets; ubSpreadIndex = GetSpreadPattern( &pFirer->inv[pFirer->ubAttackingHand] ); if( ubShots>1 && !fFake ) { fBuckshot = true; usBulletFlags |= BULLET_FLAG_BUCKSHOT; ubImpact = (UINT8) (ubImpact * AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageMultiplier / max(1,AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageDivisor) ); if (pFirer->ubTargetID != NOBODY) { MercPtrs[ pFirer->ubTargetID ]->bNumPelletsHitBy = 0; } } weapon=NULL; } //zilpin: End of new code block. // GET BULLET for (ubLoop = 0; ubLoop < ubShots; ubLoop++) { iBullet = CreateBullet( pFirer->ubID, fFake, usBulletFlags,usHandItem ); if (iBullet == -1) { //DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Failed to create bullet") ); return( FALSE ); } pBullet = GetBulletPtr( iBullet ); pBullet->sHitBy = sHitBy; if (dStartZ < WALL_HEIGHT_UNITS) { if (dEndZ > WALL_HEIGHT_UNITS) { pBullet->fCheckForRoof = TRUE; } else { pBullet->fCheckForRoof = FALSE; } } else // dStartZ >= WALL_HEIGHT_UNITS; presumably > { if (dEndZ < WALL_HEIGHT_UNITS) { pBullet->fCheckForRoof = TRUE; } else { pBullet->fCheckForRoof = FALSE; } } //zilpin: pellet spread patterns externalized in XML //Now the first shot will use the pattern, as well. //Single shot weapons will still be stuck with straight-ahead only, though. //if ( ubLoop == 0 ) if( ubShots == 1 ) { ddHorizAngle = ddOrigHorizAngle; ddVerticAngle = ddOrigVerticAngle; // first bullet, roll to hit... if ( sHitBy >= 0 ) { // calculate by hand (well, without angles) to match LOS pBullet->qIncrX = FloatToFixed( dDeltaX / (FLOAT)iDistance ); pBullet->qIncrY = FloatToFixed( dDeltaY / (FLOAT)iDistance ); pBullet->qIncrZ = FloatToFixed( dDeltaZ / (FLOAT)iDistance ); ddAdjustedHorizAngle = ddHorizAngle; ddAdjustedVerticAngle = ddVerticAngle; } else { CalculateFiringIncrements( ddHorizAngle, ddVerticAngle, d2DDistance, pBullet, &ddAdjustedHorizAngle, &ddAdjustedVerticAngle ); } } else if( ubShots > 1 ) { //Calculate the deviation due to to-hit, but only for the first shot. //The bullet is only used temporarily. It gets reset later. //Should this use d3DDistance? if( ubLoop<1 ){ CalculateFiringIncrements( ddOrigHorizAngle, ddOrigVerticAngle, d2DDistance, pBullet, &ddAdjustedHorizAngle, &ddAdjustedVerticAngle ); } // temporarily set bullet's sHitBy value to 0 to get unadjusted angles //Note: this no longer needs to be done since the CalculateFiringIncrementsSimple is being used below. //pBullet->sHitBy = 0; //Also prevent out-of-bounds overflows. //Rotates through the array if more pellets than expected, and uses pattern index 0 if requested pattern does not exist. if( gpSpreadPattern == NULL || giSpreadPatternCount<1 ){ //XML file missing, or empty, or error while loading. Use hard-coded defaults. //This is from the original code. if( ubSpreadIndex > 2 || ubSpreadIndex<0 ) ubSpreadIndex=0; n = ubLoop % BUCKSHOT_SHOTS; ddHorizAngle = ddAdjustedHorizAngle + ddShotgunSpread[ubSpreadIndex][n][0]; ddVerticAngle = ddAdjustedVerticAngle + ddShotgunSpread[ubSpreadIndex][n][1]; }else{ double d=0,r=0,xspread=0,yspread=0; int n=0; //Use spread patterns loaded from XML. if( ubSpreadIndex >= giSpreadPatternCount || ubSpreadIndex < 0) ubSpreadIndex=0; xspread=gpSpreadPattern[ubSpreadIndex].xspread; yspread=gpSpreadPattern[ubSpreadIndex].yspread; //Only use randomized spread pattern if the random spread is defined AND each static angle already fired once. if( ubLoop >= gpSpreadPattern[ubSpreadIndex].iCount && (xspread + yspread) ){ //Create random angle within range, positive and negative. switch( gpSpreadPattern[ubSpreadIndex].method ) { case SPREADPATTERNMETHOD_RECT: //Rectangle Method. (Simple) //Applying a new random number to each angle results in a rectangular spread pattern, rather than an oval one. ddHorizAngle = (double)rand() * 2 * xspread / RAND_MAX - xspread; ddVerticAngle = (double)rand() * 2 * yspread / RAND_MAX - yspread; break; case SPREADPATTERNMETHOD_DIAMOND: //Diamond Method. (Kinda Simple) //Angles are generated within a diamond shaped region. //This is more natural than the rectangular pattern, but still not optimal. //The first random number is in a range of 0 to (x+y). d = (xspread+yspread) * (double)rand() / (double)RAND_MAX; //The second random number determines the percentage of that value to use on x. The rest is spent on y. r = (double)rand() / (double)RAND_MAX; ddHorizAngle = r*d; ddVerticAngle = (1-r)*d; //Positive and negative are then randomly determined. Otherwise, everthing would always shoot to the high right (+,+). n = rand()%4; if(n&1) ddHorizAngle *= -1; if(n&2) ddVerticAngle *= -1; break; case SPREADPATTERNMETHOD_ELLIPSE: //Ellipse Method. //Angles are generated within an ellipse. //This is getting close to true spread behaviour, and may be the preferred general purpose method. //However, due to the distribution of random numbers for r, the pattern generated tends to have //more pellets land along the axis, making the pattern look a bit like the Swiss cross sometimes. //(It only becomes noticable in simulations I do in an external program. In-game it looks fine.) // //First, get our random range of -pi to pi. This could be recalculated for every use, but doesn't need to be. r=(double)rand()*2*PI/RAND_MAX - PI; //Which axis is our major axis? if( xspread > yspread ) { //The Ellipse. // Any point on an Ellipse border line = (x,y) // where x = m * cos(r) and y = n * sin(r) // where m = major axis radius and n = minor axis radius and r = all values -pi <= r <= pi //We use any random value between -pi and pi, but vary the radius to get points inside the ellipse as well. ddHorizAngle = ( (double)rand()*xspread / RAND_MAX ) * cos(r); ddVerticAngle = ( (double)rand()*yspread / RAND_MAX ) * sin(r); }else{ //Reverse sine and cosine if our major axis is y. ddHorizAngle = ( (double)rand()*xspread / RAND_MAX ) * sin(r); ddVerticAngle = ( (double)rand()*yspread / RAND_MAX ) * cos(r); } break; case -1: //Optimal Method. (Most realistic) //Not yet implemented. //Using the random distribution above, normal distrubution causes more pellets to end up toward the middle. //This is generally not noticable, and in fact feels more natural to some people (not zilpin), //but is not _even_ distribution. //In addition, since it is random, it is possible for all of the pellets to vear off center, //resulting in rare unnatural freak shots, which would never occur in a real shotgun. //This may add to gameplay, but it may bother some people (like zilpin). //Should anyone devise a way to adjust the ellipse distribution to account for this, here is where to put it. //This would probably be done by adjusting the random value of 'r' above. //Contact zilpin for real life shotgun spread pattern data, if you happen to be one of those mathematicians //who can extrapolate a succinct algorithm from raw data. But keep in mind that there's not much data to go on. // //Also note that in the real world, shot spread tends to take the shape of a sagging funnel, rather than a cone. //That is to say, the path of each pellet follows a mild curve, not a straight line. //I'm really not going to worry about that here. Ever. break; default: //If an invalid method is set in the structure, no randomized pellets are shot. //Note that there still may have been static angle pellets fired. //This should never happen, since the XML loading function will set them to giSpreadPatternMethod_Default. break; } }else if( gpSpreadPattern[ubSpreadIndex].iCount>0 ){ //Use static angle, if they exist. n = ubLoop % gpSpreadPattern[ubSpreadIndex].iCount; ddHorizAngle = gpSpreadPattern[ubSpreadIndex].x[n]; ddVerticAngle = gpSpreadPattern[ubSpreadIndex].y[n]; }else{ //No angles defined, so just fire straight. ddHorizAngle = ddVerticAngle = 0.0; } //#ifdef JA2TESTVERSION DOUBLE ddRawHorizAngle = ddHorizAngle; DOUBLE ddRawVerticAngle = ddVerticAngle; //#endif //Adjust based on the to-hit deviation calculated when the first pellet was fired. ddHorizAngle += ddAdjustedHorizAngle; ddVerticAngle += ddAdjustedVerticAngle; //Logging for debugging //#ifdef JA2TESTVERSION if(!fFake) { FILE *OutFile; if ((OutFile = fopen("SpreadPatternLog.txt", "a+t")) != NULL) { //To easily cut-and-paste these values from the log into a C/C++ source file for later analysis //Lots of reference debug info in a comment. fprintf(OutFile, "{ % 9.8f , % 9.8f , % 9.8f , % 9.8f }, //DEBUG: merc %4d fired pellet %4d of %4d using method %4d %12s with SpreadPattern %4d %s\n", ddRawHorizAngle, ddRawVerticAngle, ddHorizAngle, ddVerticAngle, pFirer->ubID, ubLoop, ubShots, gpSpreadPattern[ubSpreadIndex].method, gSpreadPatternMethodNames[gpSpreadPattern[ubSpreadIndex].method], ubSpreadIndex, gpSpreadPattern[ubSpreadIndex].Name, NULL ); fclose(OutFile); } } //#endif } //Just calculate the increments the bullet will use, not any of the to-hit adjustments, because we already did. CalculateFiringIncrementsSimple( ddHorizAngle, ddVerticAngle, pBullet ); } pBullet->ddHorizAngle = ddHorizAngle; if (ubLoop == 0 && pFirer->bDoBurst < 2) { pBullet->fAimed = TRUE; } else { // buckshot pellets after the first can hit friendlies even at close range pBullet->fAimed = FALSE; } if ( pBullet->usFlags & BULLET_FLAG_KNIFE ) { pBullet->ubItemStatus = pFirer->inv[pFirer->ubAttackingHand][0]->data.objectStatus; } // apply increments for first move //zilpin: pellet spread patterns externalized in XML //This is a bugfix for strange behavior when firing, such as the projectile hitting walls behind a merc. //The bullet should start it's journey at the beginning, not down range. //Commented out the qIncr adjustments. pBullet->qCurrX = FloatToFixed( dStartX ) ; //+ pBullet->qIncrX; pBullet->qCurrY = FloatToFixed( dStartY ) ; //+ pBullet->qIncrY; pBullet->qCurrZ = FloatToFixed( dStartZ ) ; //+ pBullet->qIncrZ; //zilpin: pellet spread patterns externalized in XML //Also bugfix. I understand why this was added, but it causes problems. /* // NB we can only apply correction for leftovers if the bullet is going to hit // because otherwise the increments are not right for the calculations! if ( pBullet->sHitBy >= 0 ) { pBullet->qCurrX += ( FloatToFixed( dDeltaX ) - pBullet->qIncrX * iDistance ) / 2; pBullet->qCurrY += ( FloatToFixed( dDeltaY ) - pBullet->qIncrY * iDistance ) / 2; pBullet->qCurrZ += ( FloatToFixed( dDeltaZ ) - pBullet->qIncrZ * iDistance ) / 2; } */ pBullet->iImpact = ubImpact; pBullet->iRange = GunRange( &(pFirer->inv[pFirer->ubAttackingHand]) ); pBullet->sTargetGridNo = ((INT32)dEndX) / CELL_X_SIZE + ((INT32)dEndY) / CELL_Y_SIZE * WORLD_COLS; pBullet->bStartCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pFirer->sGridNo ].sHeight ) ); pBullet->bEndCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dEndZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pBullet->sTargetGridNo ].sHeight ) ); // this distance limit only applies in a "hard" sense to fake bullets for chance-to-get-through, // but is used for determining structure hits by the regular code pBullet->iDistanceLimit = iDistance; // HEADROCK HAM BETA2.5: New method for signifying whether a bullet is a tracer or not, using an individual // bullet structure flag. Hehehehe, I think this is kind of reverting to old code, isn't it? if (gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) && AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect ) { UINT16 iBulletsLeft, iBulletsPerTracer; iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer; iBulletsLeft = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst; // Is this specific bullet a tracer? - based on how many tracers there are per regular bullets in // a tracer magazine (INI-settable). if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1) { pBullet->fTracer = TRUE; } else { pBullet->fTracer = FALSE; } } if (fFake) { bCTGT = FireBullet( pFirer, pBullet, TRUE ); RemoveBullet( iBullet ); return( bCTGT ); } else { // if (ubLoop + 1 > pFirer->bBulletsLeft) // { // // this is an error!! // ubLoop = ubLoop; // } //hayden if(is_client)send_bullet( pBullet, usHandItem ); FireBullet( pFirer, pBullet, FALSE ); } } //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBulletGivenTargetDone")); return( TRUE ); } INT8 ChanceToGetThrough( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ ) { if ( Item[pFirer->usAttackingWeapon].usItemClass == IC_GUN || Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE || Item[pFirer->usAttackingWeapon].rocketlauncher ) { BOOLEAN fBuckShot = FALSE; // if shotgun, shotgun would have to be in main hand if ( pFirer->inv[ HANDPOS ].usItem == pFirer->usAttackingWeapon ) { if ( AmmoTypes[pFirer->inv[ HANDPOS ][0]->data.gun.ubGunAmmoType].numberOfBullets > 1 ) { fBuckShot = TRUE; } } return( FireBulletGivenTarget( pFirer, dEndX, dEndY, dEndZ, pFirer->usAttackingWeapon, 0, fBuckShot, TRUE ) ); } else { // fake it //store the current item then replace it with a Glock 17 OBJECTTYPE oldItemInHand (pFirer->inv[ HANDPOS ]); UINT16 oldAttackingWeapon = pFirer->usAttackingWeapon; //GLOCK_17_ForUseWithLOS is init in InitializeJA2() pFirer->inv[ HANDPOS ] = GLOCK_17_ForUseWithLOS; pFirer->usAttackingWeapon = GLOCK_17; INT8 retVal = FireBulletGivenTarget( pFirer, dEndX, dEndY, dEndZ, GLOCK_17, 0, FALSE, TRUE ); pFirer->inv[ HANDPOS ] = oldItemInHand; pFirer->usAttackingWeapon = oldAttackingWeapon; return retVal; } } void MoveBullet( INT32 iBullet ) { BULLET *pBullet; FIXEDPT qLandHeight; INT32 iCurrAboveLevelZ; INT32 iCurrCubesAboveLevelZ; INT16 sDesiredLevel; INT32 iOldTileX; INT32 iOldTileY; INT32 iOldCubesZ; MAP_ELEMENT * pMapElement; STRUCTURE * pStructure; STRUCTURE * pRoofStructure = NULL; FIXEDPT qLastZ; SOLDIERTYPE * pTarget; UINT8 ubTargetID; BOOLEAN fIntended; BOOLEAN fStopped; INT8 bOldLOSIndexX; INT8 bOldLOSIndexY; UINT32 uiTileInc = 0; UINT32 uiTime; INT8 bDir; INT32 iGridNo, iAdjGridNo; INT32 iRemainingImpact; FIXEDPT qDistToTravelX; FIXEDPT qDistToTravelY; INT32 iStepsToTravelX; INT32 iStepsToTravelY; INT32 iStepsToTravel; INT32 iNumLocalStructures; INT32 iStructureLoop; UINT32 uiChanceOfHit; BOOLEAN fResolveHit; INT32 i; BOOLEAN fGoingOver = FALSE; BOOLEAN fHitStructure; FIXEDPT qWallHeight; FIXEDPT qWindowBottomHeight; FIXEDPT qWindowTopHeight; pBullet = GetBulletPtr( iBullet ); // CHECK MIN TIME ELAPSED uiTime = GetJA2Clock( ); if ( ( uiTime - pBullet->uiLastUpdate ) < pBullet->usClockTicksPerUpdate ) { return; } pBullet->uiLastUpdate = uiTime; do { // check a particular tile // retrieve values from world for this particular tile iGridNo = pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS; if (!GridNoOnVisibleWorldTile( (INT16) iGridNo ) || (pBullet->iCurrCubesZ > PROFILE_Z_SIZE * 2 && FIXEDPT_TO_INT32( pBullet->qIncrZ ) > 0 ) ) { // bullet outside of world! // NB remove bullet only flags a bullet for deletion; we still have access to the // information in the structure RemoveBullet( pBullet->iBullet ); if(ENABLE_COLLISION) BulletMissed( pBullet, pBullet->pFirer );//only if local origin return; } pMapElement = &(gpWorldLevelData[ iGridNo ]); qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) ); qWallHeight = gqStandardWallHeight + qLandHeight; qWindowBottomHeight = gqStandardWindowBottomHeight + qLandHeight; qWindowTopHeight = gqStandardWindowTopHeight + qLandHeight; // calculate which level bullet is on for suppression and close call purposes // figure out the LOS cube level of the current point iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight) ); // figure out the level if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX) { // check objects on the ground sDesiredLevel = 0; } else { // check objects on roofs sDesiredLevel = 1; } // assemble list of structures we might hit! iNumLocalStructures = 0; pStructure = pMapElement->pStructureHead; // calculate chance of hitting each structure uiChanceOfHit = ChanceOfBulletHittingStructure( pBullet->iLoop, pBullet->iDistanceLimit, pBullet->sHitBy ); if (iGridNo == (INT32) pBullet->sTargetGridNo) { fIntended = TRUE; // if in the same tile as our destination, we WANT to hit the structure! if ( fIntended ) { uiChanceOfHit = 100; } } else { fIntended = FALSE; } //check for structures at target, and calc chance to hit //hayden, disable any structure t avoid collision if(ENABLE_COLLISION) { while( pStructure ) { if (pStructure->fFlags & ALWAYS_CONSIDER_HIT) { // ALWAYS add walls // fence is special if ( pStructure->fFlags & STRUCTURE_ANYFENCE ) { // If the density of the fence is less than 100%, or this is the top of the fence, then roll the dice // NB cubes are 0 based, heights 1 based if ( pStructure->pDBStructureRef->pDBStructure->ubDensity < 100 ) { // requires roll if ( PreRandom( 100 ) < uiChanceOfHit ) { gpLocalStructure[iNumLocalStructures] = pStructure; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } } else if ( (pBullet->iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bStartCubesAboveLevelZ) && (pBullet->bEndCubesAboveLevelZ >= iCurrCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) ) { // near firer and at top of structure and at same level as bullet's start // requires roll if ( PreRandom( 100 ) < uiChanceOfHit ) { gpLocalStructure[iNumLocalStructures] = pStructure; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } } else if ( (pBullet->iLoop + pBullet->iDistanceLimit / 50 >= pBullet->iDistanceLimit - CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bEndCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) ) { // near target and at top of structure and at same level as bullet's end // requires roll if ( PreRandom( 100 ) < uiChanceOfHit ) { gpLocalStructure[iNumLocalStructures] = pStructure; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } } else { // always add gpLocalStructure[iNumLocalStructures] = pStructure; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } /* if ( !( (pStructure->pDBStructureRef->pDBStructure->ubDensity < 100 || iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) ) ) && (PreRandom( 100 ) >= uiChanceOfHit) ) { gpLocalStructure[iNumLocalStructures] = pStructure; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } */ } else { gpLocalStructure[iNumLocalStructures] = pStructure; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } } else if (pStructure->fFlags & STRUCTURE_ROOF) { // only consider roofs if the flag is set; don't add them to the array since they // are a special case if (pBullet->fCheckForRoof) { pRoofStructure = pStructure; qLastZ = pBullet->qCurrZ - pBullet->qIncrZ; // if just on going to next tile we cross boundary, then roof stops bullet here! if ( (qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight)) { // hit a roof StopBullet( pBullet->iBullet ); BulletHitStructure( pBullet, 0, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE ); return; } } } else if (pStructure->fFlags & STRUCTURE_PERSON) { if ( MercPtrs[ pStructure->usStructureID ] != pBullet->pFirer ) { // in actually moving the bullet, we consider only count friends as targets if the bullet is unaimed // (buckshot), if they are the intended target, or beyond the range of automatic friendly fire hits // OR a 1 in 30 chance occurs // ignore *intervening* target if not visible; PCs are always visible so AI will never skip them on that // basis if (fIntended) { // could hit this person! gpLocalStructure[iNumLocalStructures] = pStructure; iNumLocalStructures++; } else if ( pBullet->pFirer->flags.uiStatusFlags & SOLDIER_MONSTER ) { // monsters firing will always accidentally hit people but never accidentally hit each other. if ( !(MercPtrs[ pStructure->usStructureID ]->flags.uiStatusFlags & SOLDIER_MONSTER) ) { gpLocalStructure[iNumLocalStructures] = pStructure; iNumLocalStructures++; } } else if ( MercPtrs[ pStructure->usStructureID ]->bVisible == TRUE && gAnimControl[ MercPtrs[pStructure->usStructureID]->usAnimState ].ubEndHeight == ANIM_STAND && ( (pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS) || (!pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS_UNAIMED) || PreRandom( 100 ) < MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE ) ) { // could hit this person! gpLocalStructure[iNumLocalStructures] = pStructure; iNumLocalStructures++; } // this might be a close call if ( MercPtrs[ pStructure->usStructureID ]->bTeam == gbPlayerNum && pBullet->pFirer->bTeam != gbPlayerNum && sDesiredLevel == MercPtrs[ pStructure->usStructureID ]->pathing.bLevel ) { MercPtrs[ pStructure->usStructureID ]->flags.fCloseCall = TRUE; } if ( IS_MERC_BODY_TYPE( MercPtrs[pStructure->usStructureID] ) ) { // apply suppression, regardless of friendly or enemy // except if friendly, not within a few tiles of shooter if ( MercPtrs[ pStructure->usStructureID ]->bSide != pBullet->pFirer->bSide || pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS ) { // buckshot has only a 1 in 2 chance of applying a suppression point if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) ) { // bullet goes whizzing by this guy! switch ( gAnimControl[ MercPtrs[pStructure->usStructureID]->usAnimState ].ubEndHeight ) { case ANIM_PRONE: // two 1/4 chances of avoiding suppression pt - one below if (PreRandom( 4 ) == 0) { break; } // else fall through case ANIM_CROUCH: // 1/4 chance of avoiding suppression pt if (PreRandom( 4 ) == 0) { break; } // else fall through default: MercPtrs[pStructure->usStructureID]->ubSuppressionPoints++; MercPtrs[pStructure->usStructureID]->ubSuppressorID = pBullet->pFirer->ubID; break; } } } } } } else if ( pStructure->fFlags & STRUCTURE_CORPSE ) { if ( iGridNo == (INT32) pBullet->sTargetGridNo || (pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles >= 10) ) { // could hit this corpse! // but ignore if someone is here if ( FindStructure( (INT16) iGridNo, STRUCTURE_PERSON ) == NULL ) { gpLocalStructure[iNumLocalStructures] = pStructure; iNumLocalStructures++; } } } else { if (pBullet->iLoop > CLOSE_TO_FIRER || ( fIntended ) ) { // calculate chance of hitting structure if (PreRandom( 100 ) < uiChanceOfHit) { // could hit it gpLocalStructure[iNumLocalStructures] = pStructure; gubLocalStructureNumTimesHit[iNumLocalStructures] = 0; iNumLocalStructures++; } } } pStructure = pStructure->pNext; } } // check to see if any soldiers are nearby; those soldiers // have their near-miss value incremented if (pMapElement->ubAdjacentSoldierCnt > 0) { // cube level now calculated above! // figure out the LOS cube level of the current point //iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight) ); // figure out what level to affect... if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX) { /* if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX) { // check objects on the ground sDesiredLevel = 0; } else { // check objects on roofs sDesiredLevel = 1; } */ for( bDir = 0; bDir < NUM_WORLD_DIRECTIONS; bDir++) { iAdjGridNo = iGridNo + DirIncrementer[bDir]; if ( gubWorldMovementCosts[ iAdjGridNo ][ bDir ][ sDesiredLevel ] < TRAVELCOST_BLOCKED) { ubTargetID = WhoIsThere2( (INT16) iAdjGridNo, (INT8) sDesiredLevel ); if (ubTargetID != NOBODY) { pTarget = MercPtrs[ ubTargetID ]; if ( IS_MERC_BODY_TYPE( pTarget ) && pBullet->pFirer->bSide != pTarget->bSide ) { if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) ) { // bullet goes whizzing by this guy! switch ( gAnimControl[ pTarget->usAnimState ].ubEndHeight ) { case ANIM_PRONE: // two 1/4 chances of avoiding suppression pt - one below if (PreRandom( 4 ) == 0) { break; } // else fall through case ANIM_CROUCH: // 1/4 chance of avoiding suppression pt if (PreRandom( 4 ) == 0) { break; } // else fall through default: pTarget->ubSuppressionPoints++; pTarget->ubSuppressorID = pBullet->pFirer->ubID; break; } } /* // this could be a close call if ( pTarget->bTeam == gbPlayerNum && pBullet->pFirer->bTeam != gbPlayerNum ) { pTarget->flags.fCloseCall = TRUE; } */ } } } } } } // record old tile location for loop purposes iOldTileX = pBullet->iCurrTileX; iOldTileY = pBullet->iCurrTileY; do { // check a particular location within the tile // check for collision with the ground iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight ); //hayden not hit ground if(ENABLE_COLLISION) { if (iCurrAboveLevelZ < 0) { // ground is in the way! StopBullet( pBullet->iBullet ); BulletHitStructure( pBullet, INVALID_STRUCTURE_ID, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE ); return; } } // check for the existence of structures if (iNumLocalStructures == 0 && !pRoofStructure) { // no structures in this tile, AND THAT INCLUDES ROOFS! :-) // new system; figure out how many steps until we cross the next edge // and then fast forward that many steps. iOldTileX = pBullet->iCurrTileX; iOldTileY = pBullet->iCurrTileY; iOldCubesZ = pBullet->iCurrCubesZ; if (pBullet->qIncrX > 0) { qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE )); iStepsToTravelX = qDistToTravelX / pBullet->qIncrX; } else if (pBullet->qIncrX < 0) { qDistToTravelX = pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ); iStepsToTravelX = qDistToTravelX / (-pBullet->qIncrX); } else { // make sure we don't consider X a limit :-) iStepsToTravelX = 1000000; } if (pBullet->qIncrY > 0) { qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE )); iStepsToTravelY = qDistToTravelY / pBullet->qIncrY; } else if (pBullet->qIncrY < 0) { qDistToTravelY = pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ); iStepsToTravelY = qDistToTravelY / (-pBullet->qIncrY); } else { // make sure we don't consider Y a limit :-) iStepsToTravelY = 1000000; } // add 1 to the # of steps to travel to go INTO the next tile iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1; // special coding (compared with other versions above) to deal with // bullets hitting the ground //hayden, stop hit ground if(ENABLE_COLLISION) { if (pBullet->qCurrZ + pBullet->qIncrZ * iStepsToTravel < qLandHeight) { iStepsToTravel = __min( iStepsToTravel, abs( (pBullet->qCurrZ - qLandHeight) / pBullet->qIncrZ ) ); pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel; pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel; pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel; StopBullet( pBullet->iBullet ); BulletHitStructure( pBullet, INVALID_STRUCTURE_ID, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE ); return; } } // HEADROCK HAM B2.5: Changed condition to read fTracer flag directly from bullet's struct. // This is for the New Tracer System. if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER*/ ) ) || ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE))) { INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS; if ( pBullet->usFlags & ( BULLET_FLAG_SMALL_MISSILE ) ) { bStepsPerMove = STEPS_FOR_BULLET_MOVE_SMALL_TRAILS; } else if ( pBullet->usFlags & ( BULLET_FLAG_FLAME ) ) { bStepsPerMove = STEPS_FOR_BULLET_MOVE_FIRE_TRAILS; } for ( i = 0; i < iStepsToTravel; i++ ) { if ( ( ( pBullet->iLoop + i ) % bStepsPerMove ) == 0 ) { fGoingOver = TRUE; break; } } if ( fGoingOver ) { FIXEDPT qCurrX, qCurrY, qCurrZ; qCurrX = pBullet->qCurrX + pBullet->qIncrX * i; qCurrY = pBullet->qCurrY + pBullet->qIncrY * i; qCurrZ = pBullet->qCurrZ + pBullet->qIncrZ * i; AddMissileTrail( pBullet, qCurrX, qCurrY, qCurrZ ); } } pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel; pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel; pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel; pBullet->iLoop += iStepsToTravel; // figure out the new tile location pBullet->iCurrTileX = FIXEDPT_TO_TILE_NUM( pBullet->qCurrX ); pBullet->iCurrTileY = FIXEDPT_TO_TILE_NUM( pBullet->qCurrY ); pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) ); pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX ); pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY ); } else { // there are structures in this tile iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ ); // figure out the LOS cube level of the current point if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX) { if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX) { // check objects on the ground sDesiredLevel = STRUCTURE_ON_GROUND; } else { // check objects on roofs sDesiredLevel = STRUCTURE_ON_ROOF; iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF; } // check structures for collision for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++) { pStructure = gpLocalStructure[iStructureLoop]; if (pStructure && pStructure->sCubeOffset == sDesiredLevel) { if (((*(pStructure->pShape))[pBullet->bLOSIndexX][pBullet->bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0) { if (pStructure->fFlags & STRUCTURE_PERSON) { // hit someone! fStopped = BulletHitMerc( pBullet, pStructure, fIntended ); if (fStopped) { // remove bullet function now called from within BulletHitMerc, so just quit return; } else { // set pointer to null so that we don't consider hitting this person again gpLocalStructure[iStructureLoop] = NULL; } } else if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && pBullet->qCurrZ >= qWindowBottomHeight && pBullet->qCurrZ <= qWindowTopHeight) { fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle ); if (fResolveHit) { if (pBullet->usFlags & BULLET_FLAG_KNIFE) { // knives do get stopped by windows! iRemainingImpact = HandleBulletStructureInteraction( pBullet, pStructure, &fHitStructure ); if ( iRemainingImpact <= 0 ) { // check angle of knife and place on ground appropriately OBJECTTYPE Object; INT32 iKnifeGridNo; CreateItem( pBullet->fromItem, (INT8) pBullet->ubItemStatus, &Object ); // by default knife at same tile as window iKnifeGridNo = (INT16) iGridNo; if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT) { if ( pBullet->qIncrX > 0) { // heading east so place knife on west, in same tile } else { // place to east of window iKnifeGridNo += 1; } } else { if (pBullet->qIncrY > 0) { // heading south so place wall to north, in same tile of window } else { iKnifeGridNo += WORLD_ROWS; } } if ( sDesiredLevel == STRUCTURE_ON_GROUND ) { AddItemToPool( (INT16) iKnifeGridNo, &Object, -1, 0, 0, 0 ); } else { AddItemToPool( (INT16) iKnifeGridNo, &Object, -1, 0, 1, 0 ); } // Make team look for items NotifySoldiersToLookforItems( ); // bullet must end here! StopBullet( pBullet->iBullet ); BulletHitStructure( pBullet, pStructure->usStructureID, 1, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE ); return; } } else { if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT) { if ( pBullet->qIncrX > 0) { BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, TRUE ); LocateBullet( pBullet->iBullet ); // have to remove this window from future hit considerations so the deleted structure data can't be referenced! gpLocalStructure[ iStructureLoop ] = NULL; } else { BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, FALSE ); LocateBullet( pBullet->iBullet ); gpLocalStructure[ iStructureLoop ] = NULL; } } else { if (pBullet->qIncrY > 0) { BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, TRUE ); LocateBullet( pBullet->iBullet ); gpLocalStructure[ iStructureLoop ] = NULL; } else { BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, FALSE ); LocateBullet( pBullet->iBullet ); gpLocalStructure[ iStructureLoop ] = NULL; } } // but the bullet keeps on going!!! } } } else if ( pBullet->iLoop > CLOSE_TO_FIRER || (pStructure->fFlags & ALWAYS_CONSIDER_HIT) || (pBullet->iLoop > CLOSE_TO_FIRER) || (fIntended) ) { if (pStructure->fFlags & STRUCTURE_WALLSTUFF) { // possibly shooting at corner in which case we should let it pass fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle ); } else { fResolveHit = TRUE; } if (fResolveHit) { iRemainingImpact = HandleBulletStructureInteraction( pBullet, pStructure, &fHitStructure ); if (fHitStructure) { // ATE: NOT if we are a special bullet like a LAW trail... if (pStructure->fFlags & STRUCTURE_CORPSE && !( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT ) ) ) { // ATE: In enemy territory here... ;) // Now that we have hit a corpse, make the bugger twich! CorpseHit( (INT16)pBullet->sGridNo, pStructure->usStructureID ); DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Reducing attacker busy count..., CORPSE HIT") ); // Moved here to keep ABC >0 as long as possible RemoveBullet( iBullet ); // ReduceAttackBusyCount( ); return; } else if ( iRemainingImpact <= 0 ) { StopBullet( pBullet->iBullet ); BulletHitStructure( pBullet, pStructure->usStructureID, 1, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE ); return; } else if (fHitStructure && (gubLocalStructureNumTimesHit[iStructureLoop] == 0) ) { // play animation to indicate structure being hit BulletHitStructure( pBullet, pStructure->usStructureID, 1, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, FALSE ); gubLocalStructureNumTimesHit[iStructureLoop] = 1; } } } } } } } } // got past everything; go to next LOS location within // tile, horizontally or vertically bOldLOSIndexX = pBullet->bLOSIndexX; bOldLOSIndexY = pBullet->bLOSIndexY; iOldCubesZ = pBullet->iCurrCubesZ; do { pBullet->qCurrX += pBullet->qIncrX; pBullet->qCurrY += pBullet->qIncrY; //no hit on lan//pRoofStructure//hayden if (is_networked && pRoofStructure) { qLastZ = pBullet->qCurrZ; pBullet->qCurrZ += pBullet->qIncrZ; if ( (qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight)) { // generate roof-hitting event // always stop with roofs if ( 1 /*HandleBulletStructureInteraction( pBullet, pRoofStructure, &fHitStructure ) <= 0 */) { StopBullet( pBullet->iBullet ); BulletHitStructure( pBullet, 0, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE ); return; } /* else { // ATE: Found this: Should we be calling this because if we do, it will // delete a bullet that was not supposed to be deleted.... //BulletHitStructure( pBullet, 0, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ ); } */ } } else { pBullet->qCurrZ += pBullet->qIncrZ; } pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX ); pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY ); pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) ); pBullet->iLoop++; // HEADROCK HAM B2.5: Changed condition to read fTracer flag directly from bullet's struct. // This is for the New Tracer System. if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER */) ) || ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE))) { INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS; if (( pBullet->usFlags & ( BULLET_FLAG_SMALL_MISSILE /*| BULLET_FLAG_TRACER*/ ) ) || fTracer == TRUE) { bStepsPerMove = STEPS_FOR_BULLET_MOVE_SMALL_TRAILS; } else if ( pBullet->usFlags & ( BULLET_FLAG_FLAME ) ) { bStepsPerMove = STEPS_FOR_BULLET_MOVE_FIRE_TRAILS; } if ( pBullet->iLoop % bStepsPerMove == 0 ) { // add smoke trail AddMissileTrail( pBullet, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ ); } } } while( (pBullet->bLOSIndexX == bOldLOSIndexX) && (pBullet->bLOSIndexY == bOldLOSIndexY) && (pBullet->iCurrCubesZ == iOldCubesZ)); pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE; pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE; } } while( (pBullet->iCurrTileX == iOldTileX) && (pBullet->iCurrTileY == iOldTileY)); if ( !GridNoOnVisibleWorldTile( (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS) ) || (pBullet->iCurrCubesZ > PROFILE_Z_SIZE * 2 && FIXEDPT_TO_INT32( pBullet->qIncrZ ) > 0 ) ) { // bullet outside of world! RemoveBullet( pBullet->iBullet ); if(ENABLE_COLLISION)BulletMissed( pBullet, pBullet->pFirer );//hayden: only play event if local return; } pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY , pBullet->iCurrTileX ); uiTileInc++; if ( (pBullet->iLoop > pBullet->iRange * 2) ) { // beyond max effective range, bullet starts to drop! // since we're doing an increment based on distance, not time, the // decrement is scaled down depending on how fast the bullet is (effective range) pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2); } else if ( (pBullet->usFlags & BULLET_FLAG_FLAME) && ( pBullet->iLoop > pBullet->iRange ) ) { pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2); } // check to see if bullet is close to target if ( pBullet->pFirer->ubTargetID != NOBODY && !(pBullet->pFirer->flags.uiStatusFlags & SOLDIER_ATTACK_NOTICED) && PythSpacesAway( (INT16) pBullet->sGridNo, (INT16) pBullet->sTargetGridNo ) <= 3 ) { pBullet->pFirer->flags.uiStatusFlags |= SOLDIER_ATTACK_NOTICED; } } while( uiTileInc < pBullet->ubTilesPerUpdate ); // unless the distance is integral, after the loop there will be a // fractional amount of distance remaining which is unchecked // but we shouldn't(?) need to check it because the target is there! } INT32 CheckForCollision( FLOAT dX, FLOAT dY, FLOAT dZ, FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ, INT16 *pusStructureID, FLOAT *pdNormalX, FLOAT *pdNormalY, FLOAT *pdNormalZ ) { INT32 iLandHeight; INT32 iCurrAboveLevelZ; INT32 iCurrCubesAboveLevelZ; INT16 sDesiredLevel; MAP_ELEMENT * pMapElement; STRUCTURE * pStructure, *pTempStructure; SOLDIERTYPE * pTarget; FLOAT dTargetX; FLOAT dTargetY; FLOAT dTargetZMin; FLOAT dTargetZMax; BOOLEAN fIntended; INT16 sX, sY, sZ; FLOAT dOldZUnits, dZUnits; INT8 bLOSIndexX, bLOSIndexY; INT32 iCurrCubesZ; sX = (INT16)( dX / CELL_X_SIZE ); sY = (INT16)( dY / CELL_Y_SIZE ); sZ = (INT16)dZ; // Check if gridno is in bounds.... if ( !GridNoOnVisibleWorldTile( (INT16) (sX + sY * WORLD_COLS) ) ) { // return( COLLISION_NONE ); } if ( sX < 0 || sX > WORLD_COLS || sY < 0 || sY > WORLD_COLS ) { // return( COLLISION_NONE ); } // check a particular tile // retrieve values from world for this particular tile pMapElement = &(gpWorldLevelData[ sX + sY * WORLD_COLS] ); iLandHeight = CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ); // Calculate old height and new hieght in pixels dOldZUnits = (dZ - dDeltaZ ); dZUnits = dZ; //if (pBullet->fCheckForRoof) //{ // if (pMapElement->pRoofHead != NULL) // { // fRoofPresent = TRUE; // } // else // { // fRoofPresent = FALSE; // } //} //if (pMapElement->pMercHead != NULL && pBullet->iLoop != 1) if (pMapElement->pMercHead != NULL ) { // a merc! that isn't us :-) pTarget = pMapElement->pMercHead->pSoldier; dTargetX = pTarget->dXPos; dTargetY = pTarget->dYPos; dTargetZMin = 0.0f; CalculateSoldierZPos( pTarget, HEIGHT, &dTargetZMax ); if (pTarget->pathing.bLevel > 0) { // on roof dTargetZMin += WALL_HEIGHT_UNITS; } if ( sX + sY * WORLD_COLS == pTarget->sGridNo) { fIntended = TRUE; } else { fIntended = FALSE; } } else { pTarget = NULL; } // record old tile location for loop purposes // check for collision with the ground iCurrAboveLevelZ = (INT32) dZ - iLandHeight; if (iCurrAboveLevelZ < 0) { // ground is in the way! if ( TERRAIN_IS_WATER( pMapElement->ubTerrainID) ) { return ( COLLISION_WATER ); } else { return ( COLLISION_GROUND ); } } // check for the existence of structures pStructure = pMapElement->pStructureHead; if (pStructure == NULL) { // no structures in this tile // we can go as far as we like vertically (so long as we don't hit // the ground), but want to stop when we get to the next tile or // the end of the LOS path // move 1 unit along the bullet path //if (fRoofPresent) //{ // dLastZ = pBullet->dCurrZ; // (pBullet->dCurrZ) += pBullet->dIncrZ; // if ( (dLastZ > WALL_HEIGHT && pBullet->dCurrZ < WALL_HEIGHT) || (dLastZ < WALL_HEIGHT && pBullet->dCurrZ > WALL_HEIGHT)) // { // // generate roof-hitting event // BulletHitStructure( pBullet->pFirer, pBullet->dCurrX, pBullet->dCurrY, pBullet->dCurrZ ); // RemoveBullet( pBullet->iBullet ); // return; // } //} //else //{ // (pBullet->dCurrZ) += pBullet->dIncrZ; //} // check for ground collision if ( dZ < iLandHeight) { // ground is in the way! if ( TERRAIN_IS_WATER( pMapElement->ubTerrainID) ) { return ( COLLISION_WATER ); } else { return ( COLLISION_GROUND ); } } if ( gfCaves || gfBasement ) { if ( dOldZUnits > HEIGHT_UNITS && dZUnits < HEIGHT_UNITS ) { return( COLLISION_ROOF ); } if ( dOldZUnits < HEIGHT_UNITS && dZUnits > HEIGHT_UNITS ) { return( COLLISION_INTERIOR_ROOF ); } } // check to see if we hit someone //if (pTarget && Distance2D( dX - dTargetX, dY - dTargetY ) < HIT_DISTANCE ) //{ // well, we're in the right area; it's possible that // we're firing over or under them though // if ( dZ < dTargetZMax && dZ > dTargetZMin) // { // return( COLLISION_MERC ); // } //} } else { // there are structures in this tile iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ ); // figure out the LOS cube level of the current point // CALCULAT LOS INDEX bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( ((INT32)dX) % CELL_X_SIZE ); bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( ((INT32)dY) % CELL_Y_SIZE ); iCurrCubesZ = (INT32) CONVERT_HEIGHTUNITS_TO_INDEX( dZ ); if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX) { if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX) { // check objects on the ground sDesiredLevel = STRUCTURE_ON_GROUND; } else { // check objects on roofs sDesiredLevel = STRUCTURE_ON_ROOF; iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF; } // check structures for collision while (pStructure != NULL) { if (pStructure->fFlags & STRUCTURE_ROOF || gfCaves || gfBasement ) { if ( dOldZUnits > HEIGHT_UNITS && dZUnits < HEIGHT_UNITS ) { return( COLLISION_ROOF ); } if ( dOldZUnits < HEIGHT_UNITS && dZUnits > HEIGHT_UNITS ) { return( COLLISION_INTERIOR_ROOF ); } } if (pStructure->sCubeOffset == sDesiredLevel) { if (((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0) { *pusStructureID = pStructure->usStructureID; if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && dZ >= WINDOW_BOTTOM_HEIGHT_UNITS && dZ <= WINDOW_TOP_HEIGHT_UNITS) { if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT) { if (dDeltaX > 0) { return( COLLISION_WINDOW_SOUTHWEST ); } else { return( COLLISION_WINDOW_NORTHWEST ); } } else { if ( dDeltaY > 0) { return( COLLISION_WINDOW_SOUTHEAST ); } else { return( COLLISION_WINDOW_NORTHEAST ); } } } if (pStructure->fFlags & STRUCTURE_WALLSTUFF ) { //if ( !CalculateLOSNormal( pStructure, bLOSIndexX, bLOSIndexY, (INT8)iCurrCubesAboveLevelZ, dDeltaX, dDeltaY, dDeltaZ, pdNormalX, pdNormalY, pdNormalZ ) ) //{ // return( COLLISION_NONE ); //} *pdNormalX = 0; *pdNormalY = 0; *pdNormalZ = 0; if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT) { if (dDeltaX > 0) { *pdNormalX = -1; return( COLLISION_WALL_SOUTHEAST ); } else { *pdNormalX = 1; return( COLLISION_WALL_NORTHEAST ); } } else { if ( dDeltaY > 0) { *pdNormalY = -1; return( COLLISION_WALL_SOUTHWEST ); } else { *pdNormalY = 1; return( COLLISION_WALL_NORTHWEST ); } } } else { // Determine if we are on top of this struct // If we are a tree, not dense enough to stay! if ( !( pStructure->fFlags & STRUCTURE_TREE ) && !( pStructure->fFlags & STRUCTURE_CORPSE ) ) { if ( iCurrCubesAboveLevelZ < PROFILE_Z_SIZE-1 ) { if ( !((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[ iCurrCubesAboveLevelZ + 1 ]) ) { if ( ( pStructure->fFlags & STRUCTURE_ROOF ) ) { return( COLLISION_ROOF ); } else { return( COLLISION_STRUCTURE_Z ); } } } else { // Search next level ( if we are ground ) if ( sDesiredLevel == STRUCTURE_ON_GROUND ) { pTempStructure = pMapElement->pStructureHead; // LOOK at ALL structs on roof while ( pTempStructure != NULL ) { if (pTempStructure->sCubeOffset == STRUCTURE_ON_ROOF ) { if ( !((*(pTempStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[ 0 ]) ) { return( COLLISION_STRUCTURE_Z ); } } pTempStructure = pTempStructure->pNext; } } else { // We are very high! return( COLLISION_STRUCTURE_Z ); } } } // Check armour rating..... // ATE; not if small vegitation.... if ( pStructure->pDBStructureRef->pDBStructure->ubArmour != MATERIAL_LIGHT_VEGETATION ) { if ( !(pStructure->fFlags & STRUCTURE_CORPSE ) ) { return( COLLISION_STRUCTURE ); } } } } } pStructure = pStructure->pNext; } } // check to see if we hit someone //if (pTarget && Distance2D( dX - dTargetX, dY - dTargetY ) < HIT_DISTANCE ) //{ // well, we're in the right area; it's possible that // we're firing over or under them though // if ( dZ < dTargetZMax && dZ > dTargetZMin) // { // return( COLLISION_MERC ); // } // } } return( COLLISION_NONE ); } INT16 gsLOSDirLUT[3][3] = { 315, 0, 45, 270, 0, 90, 225, 180, 135 }; BOOLEAN CalculateLOSNormal( STRUCTURE *pStructure, INT8 bLOSX, INT8 bLOSY, INT8 bLOSZ, FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ, FLOAT *pdNormalX, FLOAT *pdNormalY, FLOAT *pdNormalZ ) { INT32 cntx, cnty; INT8 bX, bY, tX, tY; INT8 bNumNormals = 0; BOOLEAN fParimeter; vector_3 vZ, vTemp2, vNormal, vAveNormal, vTemp, vIncident; vZ.x = 0; vZ.y = 0; vZ.z = 2; vIncident.x = -1 * dDeltaX; vIncident.y = dDeltaY; vIncident.z = 0; // Nomralize vIncident = VGetNormal( &vIncident ); vAveNormal.x = 0; vAveNormal.y = 0; vAveNormal.z = 0; //OK, center on xy and goforit! for ( cntx = 0; cntx < 3; cntx++ ) { bX = bLOSX + ( cntx - 1 ); // Check for boundry conditions, use same as one before boundary if ( bX < 0 || bX > 4 ) { continue; } for ( cnty = 0; cnty < 3; cnty++ ) { bY = bLOSY + ( cnty - 1 ); if ( bY < 0 || bY > 4 ) { continue; } if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0) { fParimeter = FALSE; // THIS MUST BE THE POLYGONAL SURFACE, CHECK! do { tX = (bX - 1); tY = bY; if ( tX >= 0 ) { if ( ((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) <= 0) { fParimeter = TRUE; break; } } tX = (bX + 1); tY = bY; if ( tX <=4 ) { if (((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) > 0) { } else { fParimeter = TRUE; break; } } tX = bX; tY = bY - 1; if ( tX >= 0 ) { if (((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) > 0) { } else { fParimeter = TRUE; break; } } tX = bX; tY = bY + 1; if ( tX >=4 ) { if (((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) > 0) { } else { fParimeter = TRUE; break; } } } while( FALSE ); // OK, now add angles, but not the center! if ( cntx == 1 && cnty == 1 ) { } else //if ( fParimeter ) { // OK< Calcluate normal using cross-product // 1) Calculate Vector2 vTemp2.x = (float)(bX - bLOSX); vTemp2.y = (float)(bY - bLOSY); vTemp2.z = 1; // Skip ones ||l to incident vector //if ( vTemp2.x == vIncident.x && vTemp2.y == vIncident.y ) //{ // continue; //} //2) Calculate Normal from cross product vNormal = VCrossProduct( &vTemp2, &vZ ); if ( VGetLength( &vNormal ) > 0 ) { // Nomralize vNormal = VGetNormal( &vNormal ); // CHECK ANGLE BRTWEEN INCIDENNCE AND NORMAL //if ( VDotProduct( &vNormal, &vIncident ) > 0 ) { bNumNormals++; //Average normal! vTemp = VAdd( &vNormal, &vAveNormal ); vAveNormal = VSetEqual( &vTemp ); vAveNormal = VDivScalar( &vAveNormal, bNumNormals ); // Nomralize vAveNormal = VGetNormal( &vAveNormal ); } } } } } } *pdNormalX = 0; *pdNormalY = 0; *pdNormalZ = 0; if ( bLOSZ < 4 ) { if (((*(pStructure->pShape))[bLOSX][bLOSY] & AtHeight[ bLOSZ + 1 ]) > 0) { //*pdNormalZ = -1; } } // Average angle if ( VGetLength( &vAveNormal ) > 0 ) { *pdNormalX = vAveNormal.x; *pdNormalY = vAveNormal.y; // OK done, now determine direction if ( dDeltaX > 0 ) { *pdNormalX *= -1; } if ( dDeltaY < 0 ) { *pdNormalY *= -1; } return( TRUE ); } else { return( FALSE ); } } #if 0 { INT32 cntx, cnty; INT8 bX, bY; INT16 sAngleSum = 0; INT8 bNumSums = 0; INT16 sAngleAv; BOOLEAN fOkX, fOkY; BOOLEAN fGoodX, fGoodY; //OK, center on xy and goforit! for ( cntx = 0; cntx < 3; cntx++ ) { bX = bLOSX + ( cntx - 1 ); // Check for boundry conditions, use same as one before boundary if ( bX < 0 || bX > 4 ) { fOkX = 0; } else { fOkX = 1; } for ( cnty = 0; cnty < 3; cnty++ ) { fGoodX = fGoodY = TRUE; bY = bLOSY + ( cnty - 1 ); if ( bY < 0 || bY > 4 ) { fOkY = 0; } else { fOkY = 1; } if ( bY < 0 ) { if ( fOkX ) { bY = 0; } else { fGoodY = FALSE; } } if ( bY > 4 ) { if ( fOkX ) { bY = 4; } else { fGoodY = FALSE; } } if ( bX < 0 ) { if ( fOkY ) { bX = 0; } else { fGoodX = FALSE; } } if ( bX > 4 ) { if ( fOkX ) { bX = 4; } else { fGoodX = FALSE; } } if ( !fGoodX || !fGoodY ) { continue; } if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0) { // OK, now add angles, but not the center! if ( cntx == 1 && cnty == 1 ) { } else { sAngleSum += gsLOSDirLUT[ cntx ][cnty]; bNumSums++; } } } } // Average angle if ( bNumSums > 0) { sAngleAv = sAngleSum / bNumSums; // This is our normal in XY plane! // Get unit vector from this *pdNormalX = cos( ( (float)sAngleAv / 180 ) * PI ); *pdNormalY = sin( ( (float)sAngleAv / 180 ) * PI ); // OK done, now determine direction if ( dDeltaX > 0 ) { *pdNormalX *= -1; } if ( dDeltaY > 0 ) { *pdNormalY *= -1; } if ( bLOSZ == 3 ) { // Prependicular *pdNormalX = 0; *pdNormalY = 0; *pdNormalZ = -1; } else if ( bLOSZ > 0 && (((*(pStructure->pShape))[bLOSX][bLOSY] & AtHeight[ bLOSZ +1 ]) ) == 0) { *pdNormalX = 0; *pdNormalY = 0; *pdNormalZ = -1; } else { *pdNormalZ = 0; } return( TRUE ); } else { *pdNormalX = 0; *pdNormalY = 0; return( FALSE ); } } { INT32 cntx, cnty; INT8 bX, bY; INT16 sAngleSum = 0; INT8 bNumSums = 0; INT16 sAngleAv; BOOLEAN fAlongX, fAlongY; fAlongX = fAlongY = FALSE; //1) LOOK ALONG X bX = bLOSX - 1; bY = bLOSY; if ( bX >= 0 && bX <= 4 ) { if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 ) { fAlongX = TRUE; } } bX = bLOSX + 1; bY = bLOSY; if ( bX >= 0 && bX <= 4 ) { if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 ) { fAlongX = TRUE; } } //1) LOOK ALONG Y bX = bLOSX; bY = bLOSY - 1; if ( bY >= 0 && bY <= 4 ) { if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 ) { fAlongY = TRUE; } } bX = bLOSX; bY = bLOSY + 1; if ( bY >= 0 && bY <= 4 ) { if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 ) { fAlongY = TRUE; } } if ( fAlongX ) { *pdNormalY = 1; } else { *pdNormalY = 0; } if ( fAlongY ) { *pdNormalX = 1; } else { *pdNormalX = 0; } // OK done, now determine direction if ( dDeltaX > 0 ) { *pdNormalX *= -1; } if ( dDeltaY < 0 ) { *pdNormalY *= -1; } if ( bLOSZ < 4 && (((*(pStructure->pShape))[bLOSX][bLOSY] & AtHeight[ bLOSZ +1 ]) ) == 0) { *pdNormalX = 0; *pdNormalY = 0; *pdNormalZ = -1; } else { *pdNormalZ = 0; } if ( *pdNormalX == 0 && *pdNormalY == 0 && *pdNormalZ == 0 ) { return( FALSE ); } else { return( TRUE ); } } #endif