Files
source/Tactical/LOS.cpp
T

5563 lines
169 KiB
C++

#include "connect.h"
#ifdef PRECOMPILEDHEADERS
#include "Tactical All.h"
#else
#include "builddefines.h"
#include <stdio.h>
#include <memory.h>
#include <math.h>
#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);
}
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}
},
};
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 * 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 ) );
}
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;
FLOAT d2DDistance;
FLOAT dDeltaX;
FLOAT dDeltaY;
FLOAT dDeltaZ;
FLOAT dStartX;
FLOAT dStartY;
DOUBLE ddOrigHorizAngle;
DOUBLE ddOrigVerticAngle;
DOUBLE ddHorizAngle;
DOUBLE ddVerticAngle;
DOUBLE ddAdjustedHorizAngle;
DOUBLE ddAdjustedVerticAngle;
DOUBLE ddDummyHorizAngle;
DOUBLE ddDummyVerticAngle;
BULLET * pBullet;
INT32 iBullet;
INT32 iDistance;
UINT8 ubLoop;
UINT8 ubShots;
UINT8 ubImpact;
INT8 bCTGT;
UINT8 ubSpreadIndex = 0;
UINT16 usBulletFlags = 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;
}
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]);
// 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) );
}
}
// 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;
}
}
if ( ubLoop == 0 )
{
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
{
// temporarily set bullet's sHitBy value to 0 to get unadjusted angles
pBullet->sHitBy = 0;
ddHorizAngle = ddAdjustedHorizAngle + ddShotgunSpread[ubSpreadIndex][ubLoop][0];
ddVerticAngle = ddAdjustedVerticAngle + ddShotgunSpread[ubSpreadIndex][ubLoop][1];
CalculateFiringIncrements( ddHorizAngle, ddVerticAngle, d2DDistance, pBullet, &ddDummyHorizAngle, &ddDummyVerticAngle );
pBullet->sHitBy = sHitBy;
}
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
pBullet->qCurrX = FloatToFixed( dStartX ) + pBullet->qIncrX;
pBullet->qCurrY = FloatToFixed( dStartY ) + pBullet->qIncrY;
pBullet->qCurrZ = FloatToFixed( dStartZ ) + pBullet->qIncrZ;
// 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;
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;
}
}
if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER*/ ) ) || 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++;
if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER */) ) || 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