Files
source/Tactical/LOS.cpp
T

6097 lines
193 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 "lighting.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"
#include "WorldDat.h"
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
class SOLDIERTYPE;
#define STEPS_FOR_BULLET_MOVE_TRAILS 10
#define STEPS_FOR_BULLET_MOVE_SMALL_TRAILS 5
#define STEPS_FOR_BULLET_MOVE_FIRE_TRAILS 5
#define ALWAYS_CONSIDER_HIT (STRUCTURE_WALLSTUFF | STRUCTURE_CAVEWALL | STRUCTURE_FENCE)
extern UINT32 guiSoldierFlags;
extern INT16 DirIncrementer[8];
extern BOOLEAN fTracer;
static FIXEDPT gqStandardWallHeight = INT32_TO_FIXEDPT( WALL_HEIGHT_UNITS );
static FIXEDPT gqStandardWindowBottomHeight = INT32_TO_FIXEDPT( WINDOW_BOTTOM_HEIGHT_UNITS );
static FIXEDPT gqStandardWindowTopHeight = INT32_TO_FIXEDPT( WINDOW_TOP_HEIGHT_UNITS );
#define FIXEDPT_MULTIPLY( a, b ) ( (a / 256) * (b / 256) )
//ADB When a merc calcs CTGT for a thrown item he uses a GLOCK temp item
//but we don't want to recreate it every single time CTGT is called, so init the GLOCK in InitializeJA2()
OBJECTTYPE GLOCK_17_ForUseWithLOS;
UINT32 FPMult32(UINT32 uiA, UINT32 uiB)
{
UINT32 uiResult;
__asm {
// Load the 32-bit registers with the two values
mov eax, uiA
mov ebx, uiB
// Multiply them
// Top 32 bits (whole portion) goes into edx
// Bottom 32 bits (fractional portion) goes into eax
imul ebx
// Shift the fractional portion back to (lower) 16 bits
shr eax, 16
// Shift the whole portion to 16 bits, in the upper word
shl edx, 16
// At this point, we have edx xxxx0000 and eax 0000xxxx
// Combine the two words into a dword
or eax, edx
// Put the result into a returnable variable
mov uiResult, eax
}
return(uiResult);
}
//zilpin: pellet spread patterns externalized in XML
t_SpreadPattern *gpSpreadPattern=NULL;
INT32 giSpreadPatternCount=0;
char * gSpreadPatternMethodNames[] = {"RECTANGLE","DIAMOND","ELLIPSE",NULL};
int giSpreadPatternMethod_Default = SPREADPATTERNMETHOD_ELLIPSE;
//zilpin: ddShotgunSpread is only used as a backup if the XML file does not exist.
static DOUBLE ddShotgunSpread[3][BUCKSHOT_SHOTS][2] =
{
{
// spread of about 2 degrees in all directions
// Horiz, Vert
{ 0.0, 0.0 },
{ -0.012, 0.0 },
{ +0.012, 0.0 },
{ 0.0, -0.012},
{ 0.0, +0.012},
{ -0.008, -0.008},
{ -0.008, +0.008},
{ +0.008, -0.008},
{ +0.008, +0.008}
},
{
// duckbill flattens the spread and makes it wider horizontally (5 degrees)
// Horiz, Vert
{ 0.0, 0.0 },
{ -0.008, 0.0 },
{ +0.008, 0.0 },
{ -0.016, 0.0 },
{ +0.016, 0.0 },
{ -0.024, 0.0 },
{ +0.024, 0.0 },
{ -0.032, 0.0 },
{ +0.032, 0.0 },
},
{
// flamethrower more spread out
// Horiz, Vert
{ 0.0, 0.0 },
{ -0.120, 0.0 },
{ +0.120, 0.0 },
{ 0.0, -0.120},
{ 0.0, +0.120},
{ -0.080, -0.080},
{ -0.080, +0.080},
{ +0.080, -0.080},
{ +0.080, +0.080}
},
};
//zilpin: pellet spread patterns externalized in XML
//Decide what spread pattern to use for an object in inventory.
//Attachment trumps Weapon trumps Ammo trumps AmmoType
//If multiple attachments, then the first one encountered is used.
//This will work for non-weapons, but ammo and ammo type are only checked for guns.
INT32 GetSpreadPattern( OBJECTTYPE * pObj )
{
int i=0,n=0;
if( !pObj || !pObj->usItem || !pObj->exists() ) return 0;
//If there are attachments, check them. Stop on the first one with something defined.
//Dear God, I hate C++ iterators. What a fugly mess.
for (attachmentList::iterator iter = pObj[0][0]->attachments.begin(); iter != pObj[0][0]->attachments.end(); ++iter){
if( n=Item[ iter->usItem ].spreadPattern )
//An attachment has it, and it trumps everything, so return it's value.
return n;
}
//The object's own spread pattern, if it has one.
if( n=Item[pObj->usItem].spreadPattern )
return n;
//If a gun, and don't have a pattern yet, check it's ammo.
if( IC_GUN & Item[pObj->usItem].usItemClass ){
//Check for the ammo first.
if( n=Item[ pObj[0][0]->data.gun.usGunAmmoItem ].spreadPattern )
return n;
//If no pattern for the ammo, try the ammo type.
if( n=AmmoTypes[pObj[0][0]->data.gun.ubGunAmmoType].spreadPattern )
return n;
}
/*
This is the old code to iterate through all the attachments.
No longer works, since the NewInv project object-orientified everything.
*
//If there are attachments, check them. Stop on the first one with something defined.
for (i=0; i<MAX_ATTACHMENTS; i++)
if (pObj->usAttachItem[i])
if( n=Item[ pObj->usAttachItem[i] ].spreadPattern )
//The attachment has it, and it trumps everything, so return it's value.
return n;
//The object's own spread pattern, if it has one.
if( n=Item[pObj->usItem].spreadPattern )
return n;
//If a gun, and don't have a pattern yet, check it's ammo.
if( IC_GUN & Item[pObj->usItem].usItemClass ){
//Check for the ammo first.
if( n=Item[ pObj->ItemData.Gun.usGunAmmoItem ].spreadPattern )
return n;
//If no pattern for the ammo, try the ammo type.
if( n=AmmoTypes[pObj->ItemData.Gun.ubGunAmmoType].spreadPattern )
return n;
}
*/
//No pattern for anything found, so use the default.
return 0;
}
static UINT8 gubTreeSightReduction[ANIM_STAND + 1] =
{
0,
8, // prone
0,
7, // crouched
0,
0,
6 // standing
};
#define MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE 25
#define MAX_CHANCE_OF_HITTING_STRUCTURE 90
static UINT32 guiStructureHitChance[ MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE + 1] =
{
0, // 0 tiles
0,
0,
2,
4,
7, // 5 tiles
10,
14,
18,
23,
28, // 10 tiles
34,
40,
47,
54,
60, // 15 tiles
66,
71,
74,
76,
78,// 20 tiles
80,
82,
84,
86,
88,// 25 tiles
};
#define PERCENT_BULLET_SLOWED_BY_RANGE 25
#define MIN_DIST_FOR_HIT_FRIENDS 30
#define MIN_DIST_FOR_HIT_FRIENDS_UNAIMED 15
#define MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE 3
#define RADIANS_IN_CIRCLE 6.283
#define DEGREES_22_5 (RADIANS_IN_CIRCLE * 22.5 / 360)
#define DEGREES_45 (RADIANS_IN_CIRCLE * 45 / 360)
// note: these values are in RADIANS!!
// equal to 15 degrees
#define MAX_AIMING_SCREWUP (RADIANS_IN_CIRCLE * 15 / 360)
// min aiming screwup is X degrees, gets divided by distance in tiles
#define MIN_AIMING_SCREWUP (RADIANS_IN_CIRCLE * 22 / 360)
//#define MAX_AIMING_SCREWUP 0.2618
// equal to 10 degrees
//#define MAX_AIMING_SCREWUP_VERTIC 0.1745
#define SMELL_REDUCTION_FOR_NEARBY_OBSTACLE 20 //80
#define STANDING_CUBES 3
// MoveBullet and ChanceToGetThrough use this array to maintain which
// of which structures in a tile might be hit by a bullet.
#define MAX_LOCAL_STRUCTURES 100
STRUCTURE * gpLocalStructure[MAX_LOCAL_STRUCTURES];
UINT32 guiLocalStructureCTH[MAX_LOCAL_STRUCTURES];
UINT8 gubLocalStructureNumTimesHit[MAX_LOCAL_STRUCTURES];
BOOLEAN CalculateLOSNormal( STRUCTURE *pStructure, INT8 bLOSX, INT8 bLOSY, INT8 bLOSZ, FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ, FLOAT *pdNormalX, FLOAT *pdNormalY, FLOAT *pdNormalZ );
extern UINT8 gubMaterialArmour[];
#ifdef LOS_DEBUG
LOSResults gLOSTestResults = {0};
#endif
FIXEDPT FloatToFixed( FLOAT dN )
{
FIXEDPT qN;
// verify that dN is within the range storable by FIXEDPT?
// first get the whole part
qN = (INT32) (dN * FIXEDPT_FRACTIONAL_RESOLUTION);
//qN = INT32_TO_FIXEDPT( (INT32)dN );
// now add the fractional part
//qN += (INT32)(((dN - (INT32) dN)) * FIXEDPT_FRACTIONAL_RESOLUTION);
return( qN );
}
FLOAT FixedToFloat( FIXEDPT qN )
{
return( ((FLOAT) qN) / FIXEDPT_FRACTIONAL_RESOLUTION );
}
//
// fixed-point arithmetic stuff ends here
//
FLOAT Distance3D( FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ )
{
return( (FLOAT) sqrt( (DOUBLE) (dDeltaX * dDeltaX + dDeltaY * dDeltaY + dDeltaZ * dDeltaZ) ));
}
FLOAT Distance2D( FLOAT dDeltaX, FLOAT dDeltaY )
{
return( (FLOAT) sqrt( (DOUBLE) (dDeltaX * dDeltaX + dDeltaY * dDeltaY )));
}
//#define DEBUGLOS
#if defined( JA2BETAVERSION ) && defined( DEBUGLOS )
void DebugLOS( STR szOutput )
{
DebugMsg(TOPIC_JA2,DBG_LEVEL_3,szOutput);
FILE * DebugFile;
if ((DebugFile = fopen( "losdebug.txt", "a+t" )) != NULL)
{
fputs( szOutput, DebugFile );
fputs( "\n", DebugFile );
fclose( DebugFile );
}
}
#else
#define DebugLOS( a )
#endif
enum
{
LOC_OTHER,
LOC_0_4,
LOC_3_4,
LOC_4_0,
LOC_4_3,
LOC_4_4
}
LocationCode;
// TODO: Relocate functions in appropriate class (soldiertype obviously)
/**
* Calculates the height and gives it back as one of the ANIM_* defines
*
* @comment I don't want to repetitivly use code that should be part of the pSoldier class, so I made a oo-c-based-method
*
* @param pSoldier
* @return ANIM_STAND or ANIM_CROUCH or ANIM_PRONE
*/
inline UINT8 GetCurrentHeightOfSoldier( SOLDIERTYPE* pSoldier )
{
return gAnimControl[ pSoldier->usAnimState ].ubHeight;
}
// why not functions? because these are typeless
#define MINMAX(iMin, iMax, iVal) min(iMax, max(iMin, iVal))
#define MINMAX100(iVal) MINMAX(0, 100, iVal)
#define MINMAX100N(iVal) MINMAX(-100, 100, iVal)
/*
* Calculates the total camouflage for the corresponding type.
*
* @comment I don't want to repetitivly use code that should be part of the pSoldier class, so I made a oo-c-based-method
* @comment we don't need the snow camo as we don't have a terrain type -> camouflage association for snow
* (meaning we would need externalisation for specifing camo effectivness based on terrain textures and not terrain types)
* (unfortunatly that's not only programming but alot of content-heavy work, which doesn't seem probable to happen)
* @precondition camo on soldiertypes are correctly set
*
* @param pSoldier
* @return a value generally between 0 and 100 (but who knows if someone wants to use negative values, the basic types allow for that)
*/
inline INT8 GetJungleCamouflage( SOLDIERTYPE* pSoldier )
{
if (HAS_SKILL_TRAIT( pSoldier, CAMOUFLAGED )) {
return 100;
}
return MINMAX100N(pSoldier->bCamo + pSoldier->wornCamo);
}
inline INT8 GetDesertCamouflage( SOLDIERTYPE* pSoldier )
{
if (HAS_SKILL_TRAIT( pSoldier, CAMOUFLAGED_DESERT )) {
return 100;
}
return MINMAX100N(pSoldier->desertCamo + pSoldier->wornDesertCamo);
}
inline INT8 GetUrbanCamouflage( SOLDIERTYPE* pSoldier )
{
if (HAS_SKILL_TRAIT( pSoldier, CAMOUFLAGED_URBAN )) {
return 100;
}
return MINMAX100N(pSoldier->urbanCamo + pSoldier->wornUrbanCamo);
}
/**
* Calculates the total stealth value of a player.
*
* @comment I don't want to repetitivly use code that should be part of the pSoldier class, so I made a oo-c-based-method
*
* @param pSoldier
* @return ANIM_STAND or ANIM_CROUCH or ANIM_PRONE
*/
inline INT8 GetStealth( SOLDIERTYPE* pSoldier )
{
INT16 stealth = GetWornStealth( pSoldier );
if (HAS_SKILL_TRAIT( pSoldier, STEALTHY ))
{
stealth += gGameExternalOptions.ubStealthTraitCoverValue * NUM_SKILL_TRAITS( pSoldier, STEALTHY );
}
return MINMAX100N(stealth);
}
/**
* Gives back a valid terrainId for a specified GridNo
*
* @comment I don't want to repetitivly use code that should be part of the pSoldier class, so I made a oo-c-based-method
* @comment the current method GetTerrainType doesn't check for more info, but for camouflage we need that extra info
*
* @param pSoldier
* @return ANIM_STAND or ANIM_CROUCH or ANIM_PRONE
*/
INT8 GetTerrainTypeForGrid( const UINT16& sGridNo )
{
if (gpWorldLevelData[sGridNo].ubTerrainID != NO_TERRAIN)
{
return gpWorldLevelData[sGridNo].ubTerrainID;
}
// Check if we have anything in object layer which has a terrain modifier
LEVELNODE* pNode = gpWorldLevelData[ sGridNo ].pObjectHead;
if ( pNode != NULL )
{
if ( gTileDatabase[ pNode->usIndex ].ubTerrainID != NO_TERRAIN )
{
return gTileDatabase[ pNode->usIndex ].ubTerrainID ;
}
}
// Now try terrain!
pNode = gpWorldLevelData[ sGridNo ].pLandHead;
return gTileDatabase[ pNode->usIndex ].ubTerrainID;
}
// the following functions should return percentage values which can be easily added/substracted from
// the sight reduction variable inside the Soldier-sight-test.
// This means we specify here how much each test counts towards the total.
// e.g. camouflage of 100 at stance prone on perfect terrain will give back "-50" as value, indicating a 50% sight reduction
/**
* Calculates a percentage value to be added (or substracted) from sight. it's based on the camouflage and stance of the soldier, as well as the type of terrain.
* - usually it should return something smaller than 0 but we might give a bonus in a later version if the target wears a red-glowing hat
*
* @param pSoldier the target needed to get the correct camouflage info
* @param ubTerrainType terrain type defined by TerrainTypeDefines
* @return a negative value will indicate a reduction of sight, a positive one an addition to sight
*/
INT8 GetSightAdjustmentCamouflageOnTerrain( SOLDIERTYPE* pSoldier, const UINT8& ubStance, const UINT8& ubTerrainType )
{
if (gGameExternalOptions.ubCamouflageEffectiveness == 0) {
return 0;
}
// current implementation will assume the return will be below 0 and then scale it depending on your stance
INT8 scaler = -(ANIM_STAND - ubStance) * 5; // stand = 0%, crouch = 6-3 = 30%, prone = 6-1 = 50;
// last term corresponds to the maximum of scaler before
scaler *= gGameExternalOptions.ubCamouflageEffectiveness / 50;
switch(ubTerrainType) {
case LOW_GRASS:
case HIGH_GRASS:
return GetJungleCamouflage(pSoldier) * scaler/100;
case FLAT_FLOOR:
case PAVED_ROAD:
return GetUrbanCamouflage(pSoldier) * scaler/100;
case DIRT_ROAD:
case TRAIN_TRACKS:
return GetDesertCamouflage(pSoldier) * scaler/100;
case FLAT_GROUND:
// here it would be best if we could have a terrain texture -> camo association instead of terrain type -> camo
// but we need to allow modders to specify that information in an xml file
// this is because flat ground is used by both (it's irritating to see wood camo work perfectly on brown ground, at least ingame)
return max( GetDesertCamouflage(pSoldier), GetJungleCamouflage(pSoldier) ) * scaler/100;
default:
return 0;
}
}
/**
* Calculates a percentage value to be added (or substracted) from sight. it's based on the movement of a soldier and the brightness level.
* - this will be used to calculate the penalty (sight addition) which occurs on the soldier who is been spot-tested
* - it also will be used to reduce the ability to find others if the spotter moves
*
* @param pSoldier the target
* @return a negative value will indicate a reduction of sight, a positive one an addition to sight
*/
INT8 GetSightAdjustmentThroughMovement( SOLDIERTYPE* pSoldier, const INT8& bTilesMoved, const UINT8& ubLightlevel )
{
if (!gGameExternalOptions.fMovementSightAdjustment) {
return 0;
}
INT8 stealth = GetStealth(pSoldier);
INT8 bMovementAdjustment = bTilesMoved * ( 100 - stealth ) / 100;
UINT8 ubBrightness = SHADE_MIN - ubLightlevel; // 3: starlight, 5: evening dark, 15: day
UINT8 ubScalerInPercent = 6 * ubBrightness; //0:0, 15:90
return bMovementAdjustment * ubScalerInPercent / 100;
}
INT8 GetSightAdjustmentThroughStance( const UINT8& ubStance )
{
if (!gGameExternalOptions.fStanceSightAdjustment) {
return 0;
}
// well why do we have a positive number here?
// just because I wanted to have logic which goes both directions
INT8 bStanceAdjustment = (ubStance - 3) * 2; // stand = 15%, crouch = 0%, prone = -10%
return bStanceAdjustment;
}
/**
* TODO: Figure out how to find out your equipment cost. Big equipment will make you more visible! we could go after the load itself (kg)
* TODO: but we still want a backpack to count more than your vest
*
* Calculates a percentage value to be added (or substracted) from sight. it's based on the amount of LBE.
*
* @param pSoldier the target
* @return a negative value will indicate a reduction of sight, a positive one an addition to sight
*/
INT8 GetSightAdjustmentBasedOnLBE( SOLDIERTYPE* pSoldier )
{
if (!gGameExternalOptions.fLBESightAdjustment) {
return 0;
}
// ....
//if(UsingNewInventorySystem() == true && pSoldier->inv[BPACKPOCKPOS].exists() == true) {
//pSoldier->inv[BPACKPOCKPOS]
//CalculateObjectWeight
//}
return 0;
}
/**
* Calculates a percentage value to be added (or substracted) from sight. it's based on the stealth value and the brightness.
* - usually it should return something smaller than 0 but we might give a bonus if the target uses a lamp for better viewing (LAM-Lamp) which the spotter can see
*
* @param pSoldier the target
* @param ubLightLevel light level given back by the LightTrueLevel function
* @return a negative value will indicate a reduction of sight, a positive one an addition to sight
*/
INT8 GetSightAdjustmentStealthAtLightLevel( SOLDIERTYPE* pSoldier, const UINT8& ubLightlevel )
{
if (gGameExternalOptions.ubStealthEffectiveness == 0) {
return 0;
}
INT8 ibStealthInPercent = GetStealth( pSoldier );
// set scaler to scale with light level
UINT8 ubBrightness = SHADE_MIN - ubLightlevel; // 3: starlight, 5: evening dark, 15: day
UINT8 ubScaler = -4 * ubBrightness + 60; //0:60, 15:0
// last term corresponds to the maximum of ubScaler before
ubScaler *= gGameExternalOptions.ubStealthEffectiveness / 60;
return (INT8) MINMAX100N( - ibStealthInPercent * ubScaler / 100);
}
/**
* Calculates a percentage value used for trees that hide you. Will be used inside line of sight function.
* - It's based on the tree "height", fill and density.
* - As well as range and you point of intersection
* - can go up to 100% hiding
*
* @param iRange one tile = 10 range
* @param pTreeStructure a structure containing the tree you want to check
* @param ubHeightLevel between 1 and 4 of the current merc
* @return >=0 the percentage a tree will give a bonus to hiding / view range degradation
*/
INT8 GetSightAdjustmentBehindStructure( const INT16& iRange, STRUCTURE* pStructure, const UINT8& ubHeightLevel )
{
if (gGameExternalOptions.ubTreeCoverEffectiveness == 0) {
return 0;
}
// Assertions here because we might need this function in other line of sight tests
Assert( ubHeightLevel >= 1 );
Assert( ubHeightLevel <= 4 );
Assert( pStructure != NULL );
Assert( iRange >= 0 ); // ==0 for you being inside a passable tree
// pStructure->fFlags & STRUCTURE_TREE
UINT8 ubLevels[4];
if (StructureDensity( pStructure, &ubLevels[0], &ubLevels[1], &ubLevels[2], &ubLevels[3] ) != TRUE)
{
return 0; // this structure has no density therefore no additional sight adjustment possible
}
//const UINT8& ubDensityInPercent = pStructure->pDBStructureRef->pDBStructure->ubDensity;
UINT16 ubFillInPercent = ubLevels[ubHeightLevel-1];
Assert( ubFillInPercent >= 0 );
Assert( ubFillInPercent <= 100 );
// these scalers are for backwards compatibility to mods
UINT8 ubCloseRangeScaler = iRange > CLOSE_TO_FIRER; // zero or 1
INT16 ubLongRangeScaler = iRange > 100 ? iRange / 100 : 1;
//* ubDensityInPercent / 100 we assume densitiy is used for bullets
// we make the fill less effective
INT8 ubSolidityInPercent = ubFillInPercent * gGameExternalOptions.ubTreeCoverEffectiveness / 100;
INT8 ubResult = MINMAX100N(- ubSolidityInPercent * ubCloseRangeScaler * ubLongRangeScaler);
return ubResult;
}
/*
* Easy way to get all sight adjustments into one that affect a soldier
*/
INT16 GetSightAdjustment( SOLDIERTYPE* pSoldier, INT16 sGridNo, INT8 bStance )
{
if (sGridNo == -1) {
sGridNo = pSoldier->sGridNo;
}
if (bStance == -1) {
bStance = GetCurrentHeightOfSoldier( pSoldier );
}
UINT8 ubTerrainType = GetTerrainTypeForGrid( sGridNo );
UINT8 ubLightLevel = LightTrueLevel( sGridNo, gsInterfaceLevel );
INT16 iSightAdjustment = 0;
// general stuff (independant of soldier)
iSightAdjustment += GetSightAdjustmentThroughStance( bStance );
// context sensitive (needs soldier)
iSightAdjustment += GetSightAdjustmentBasedOnLBE( pSoldier );
// context sensitive stuff with 2nd parameter (needs soldier for attributes but can be given a second parameter)
iSightAdjustment += GetSightAdjustmentThroughMovement( pSoldier, pSoldier->bTilesMoved, ubLightLevel );
iSightAdjustment += GetSightAdjustmentStealthAtLightLevel( pSoldier, ubLightLevel );
iSightAdjustment += GetSightAdjustmentCamouflageOnTerrain( pSoldier, bStance, ubTerrainType );
return MINMAX100N(iSightAdjustment);
}
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, INT8 bAware, 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));
// 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)
{
INT8 adjustment = GetSightAdjustmentBehindStructure( iLoop, pStructure, iCurrCubesAboveLevelZ+1 );
iAdjSightLimit = iAdjSightLimit + iAdjSightLimit * adjustment/100;
#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 );
}
// this is the only function which actually uses the SightAdjustment directly, as it knows that you want to point to a soldier and therefore it can
// calculate all stealthy stuff of that soldier so the "start soldier" cant see him that good
INT32 SoldierToSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, INT8 bAware, int iTileSightLimit, UINT8 ubAimLocation, bool adjustForSight )
{
FLOAT dStartZPos, dEndZPos;
BOOLEAN fOk;
BOOLEAN fSmell;
UINT8 ubPosType;
// 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 );
}
}
}
// needed for sight limit calculation
if (iTileSightLimit == CALC_FROM_ALL_DIRS || iTileSightLimit == CALC_FROM_WANTED_DIR) {
iTileSightLimit = pStartSoldier->GetMaxDistanceVisible( pEndSoldier->sGridNo, pEndSoldier->pathing.bLevel, iTileSightLimit );
iTileSightLimit += iTileSightLimit * GetSightAdjustment(pEndSoldier) / 100;
}
// needed for gun hit calculation (can you even hit him)
else if (iTileSightLimit == NO_DISTANCE_LIMIT) {
iTileSightLimit = pStartSoldier->GetMaxDistanceVisible( pEndSoldier->sGridNo, pEndSoldier->pathing.bLevel, CALC_FROM_ALL_DIRS );
iTileSightLimit += iTileSightLimit * GetSightAdjustment(pEndSoldier) / 100;
iTileSightLimit += 255; // this shifts the limit for something special (we don't know yet)
}
// we assume that if we are given a limit it doesn't include stealth or similar stuff
// for other function we assume the opposite but not this one, as we here are given the needed target soldier information to calculate sight adjustment
else {
iTileSightLimit += iTileSightLimit * GetSightAdjustment(pEndSoldier) / 100;
}
return( LineOfSightTest( (FLOAT) CenterX( pStartSoldier->sGridNo ), (FLOAT) CenterY( pStartSoldier->sGridNo ), dStartZPos, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos, iTileSightLimit, bAware, 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, TRUE, 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, bAware, 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, bAware, HasThermalOptics( pStartSoldier), NULL, false ) );
}
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, bAware, FALSE, NULL ) );
}
/*
INT32 BulletImpactReducedByRange( INT32 iImpact, INT32 iDistanceTravelled, INT32 iRange )
{
// for now, don't reduce, because did weird stuff to AI!
return( iImpact );
// only start reducing impact at distances greater than one range
//return( __max( 1, iImpact * ( 100 - ( PERCENT_BULLET_SLOWED_BY_RANGE * iDistanceTravelled ) / iRange ) / 100 ) );
}
*/
BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntended )
{
INT32 iImpact, iDamage;
EV_S_WEAPONHIT SWeaponHit;
INT16 sRange;
SOLDIERTYPE * pFirer = pBullet->pFirer;
FLOAT dZPosRelToMerc;
UINT8 ubHitLocation = AIM_SHOT_RANDOM;
UINT8 ubAttackDirection;
UINT8 ubAmmoType;
UINT32 uiChanceThrough;
UINT8 ubSpecial = FIRE_WEAPON_NO_SPECIAL;
INT16 sHitBy;
BOOLEAN fStopped = TRUE;
INT8 bSlot;
INT8 bHeadSlot = NO_SLOT;
SOLDIERTYPE * pTarget;
INT16 sNewGridNo;
BOOLEAN fCanSpewBlood = FALSE;
INT8 bSpewBloodLevel;
// structure IDs for mercs match their merc IDs
pTarget = MercPtrs[ pStructure->usStructureID ];
if (pBullet->usFlags & BULLET_FLAG_KNIFE)
{
// Place knife on guy....
// See if they have room ( and make sure it's not in hand pos?
// CHRISL:
bSlot = FindEmptySlotWithin( pTarget, BIGPOCKSTART, NUM_INV_SLOTS );
if (bSlot == NO_SLOT)
{
// Add item
CreateItem( pBullet->fromItem, (INT8) pBullet->ubItemStatus, &gTempObject );
AddItemToPool( pTarget->sGridNo, &gTempObject, -1 , pTarget->pathing.bLevel, 0, 0 );
// Make team look for items
NotifySoldiersToLookforItems( );
}
else
{
if ( Item[pBullet->fromItem].bloodieditem > 0 )
CreateItem( Item[pBullet->fromItem].bloodieditem, (INT8) pBullet->ubItemStatus, &(pTarget->inv[bSlot]) );
else
CreateItem( pBullet->fromItem, (INT8) pBullet->ubItemStatus, &(pTarget->inv[bSlot]) );
}
ubAmmoType = AMMO_KNIFE;
}
else
{
ubAmmoType = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType;
}
// at least partly compensate for "near miss" increases for this guy, after all, the bullet
// actually hit him!
// take this out for now at least... no longer certain that he was awarded a suppression pt
// when the bullet got near him
//pTarget->ubSuppressionPoints--;
if ( pTarget->flags.uiStatusFlags & SOLDIER_VEHICLE || (pTarget->ubBodyType == COW || pTarget->ubBodyType == CROW || pTarget->ubBodyType == BLOODCAT) )
{
//ubHitLocation = pStructure->ubVehicleHitLocation;
ubHitLocation = AIM_SHOT_TORSO;
}
else
{
// Determine where the person was hit...
if ( CREATURE_OR_BLOODCAT( pTarget ) )
{
ubHitLocation = AIM_SHOT_TORSO;
// adult monster types have a weak spot
if ( (pTarget->ubBodyType >= ADULTFEMALEMONSTER) && (pTarget->ubBodyType <= YAM_MONSTER) )
{
ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->pFirer->sGridNo, pTarget->sGridNo );
if ( ubAttackDirection == pTarget->ubDirection || ubAttackDirection == gOneCCDirection[ pTarget->ubDirection ] || ubAttackDirection == gOneCDirection[ pTarget->ubDirection ] )
{
// may hit weak spot!
if (0) // check fact
{
uiChanceThrough = 30;
}
else
{
uiChanceThrough = 1;
}
if (PreRandom( 100 ) < uiChanceThrough)
{
ubHitLocation = AIM_SHOT_GLAND;
}
}
}
}
if (ubHitLocation == AIM_SHOT_RANDOM) // i.e. if not set yet
{
if (pTarget->MercInDeepWater( ) )
{
// automatic head hit!
ubHitLocation = AIM_SHOT_HEAD;
}
else
{
switch (gAnimControl[ pTarget->usAnimState ].ubEndHeight)
{
case ANIM_STAND:
// Fall through to crouch if in shallow or medium water
if ( !pTarget->MercInShallowWater( ) )
{
dZPosRelToMerc = FixedToFloat( pBullet->qCurrZ ) - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pBullet->sGridNo].sHeight );
if ( dZPosRelToMerc > HEIGHT_UNITS )
{
dZPosRelToMerc -= HEIGHT_UNITS;
}
if (dZPosRelToMerc > STANDING_HEAD_BOTTOM_POS)
{
ubHitLocation = AIM_SHOT_HEAD;
}
else if (dZPosRelToMerc < STANDING_TORSO_BOTTOM_POS )
{
ubHitLocation = AIM_SHOT_LEGS;
}
else
{
ubHitLocation = AIM_SHOT_TORSO;
}
break;
}
case ANIM_CROUCH:
dZPosRelToMerc = FixedToFloat( pBullet->qCurrZ ) - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[pBullet->sGridNo].sHeight );
if ( dZPosRelToMerc > HEIGHT_UNITS )
{
dZPosRelToMerc -= HEIGHT_UNITS;
}
if (dZPosRelToMerc > CROUCHED_HEAD_BOTTOM_POS)
{
ubHitLocation = AIM_SHOT_HEAD;
}
else if ( dZPosRelToMerc < CROUCHED_TORSO_BOTTOM_POS )
{
// prevent targets in water from being hit in legs
ubHitLocation = AIM_SHOT_LEGS;
}
else
{
ubHitLocation = AIM_SHOT_TORSO;
}
break;
case ANIM_PRONE:
ubHitLocation = AIM_SHOT_TORSO;
break;
}
}
}
if ( ( AmmoTypes[ubAmmoType].monsterSpit ) && (ubHitLocation == AIM_SHOT_HEAD) && ( ! (pTarget->flags.uiStatusFlags & SOLDIER_MONSTER) ) )
{
UINT8 ubOppositeDirection;
ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->pFirer->sGridNo, pTarget->sGridNo );
ubOppositeDirection = gOppositeDirection[ ubAttackDirection ];
if ( ! ( ubOppositeDirection == pTarget->ubDirection || ubAttackDirection == gOneCCDirection[ pTarget->ubDirection ] || ubAttackDirection == gOneCDirection[ pTarget->ubDirection ] ) )
{
// lucky bastard was facing away!
}
// else if ( ( (pTarget->inv[HEAD1POS].usItem == NIGHTGOGGLES) || (pTarget->inv[HEAD1POS].usItem == SUNGOGGLES) || (pTarget->inv[HEAD1POS].usItem == GASMASK) ) && ( PreRandom( 100 ) < (UINT32) (pTarget->inv[HEAD1POS][0]->data.objectStatus) ) )
else if ( ( (pTarget->inv[HEAD1POS].exists() == true) ) && ( PreRandom( 100 ) < (UINT32) (pTarget->inv[HEAD1POS][0]->data.objectStatus) ) )
{
// lucky bastard was wearing protective stuff
bHeadSlot = HEAD1POS;
}
else if ( ( (pTarget->inv[HEAD2POS].exists() == true) ) && ( PreRandom( 100 ) < (UINT32) (pTarget->inv[HEAD2POS][0]->data.objectStatus) ) )
{
// lucky bastard was wearing protective stuff
bHeadSlot = HEAD2POS;
}
else
{
// splat!!
ubSpecial = FIRE_WEAPON_BLINDED_BY_SPIT_SPECIAL;
}
}
}
// Determine damage, checking guy's armour, etc
sRange = (INT16)GetRangeInCellCoordsFromGridNoDiff( pFirer->sGridNo, pTarget->sGridNo );
if ( gTacticalStatus.uiFlags & GODMODE && !(pFirer->flags.uiStatusFlags & SOLDIER_PC))
{
// in god mode, and firer is computer controlled
iImpact = 0;
iDamage = 0;
}
else if (fIntended)
{
if (pFirer->aiData.bOppList[pTarget->ubID] == SEEN_CURRENTLY)
{
sHitBy = pBullet->sHitBy;
}
else
{
// hard to aim at something far away being reported by someone else!
sHitBy = pBullet->sHitBy / 2;
}
// hit the intended target which was in our LOS
// reduce due to range
iImpact = pBullet->iImpact; //BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
iImpact -= pBullet->iImpactReduction;
if (iImpact < 0)
{
// shouldn't happen but
iImpact = 0;
}
iDamage = BulletImpact( pFirer, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial );
// handle hit here...
if( ( pFirer->bTeam == 0 ) )
{
gMercProfiles[ pFirer->ubProfile ].usShotsHit++;
}
// intentionally shot
pTarget->flags.fIntendedTarget = TRUE;
if ( (pBullet->usFlags & BULLET_FLAG_BUCKSHOT) && ( pTarget->ubID == pFirer->ubTargetID ) )
{
pTarget->bNumPelletsHitBy++;
}
}
else
{
// if an accidental target was hit, don't give a bonus for good aim!
sHitBy = 0;
iImpact = pBullet->iImpact;
//iImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
iImpact -= pBullet->iImpactReduction;
if (iImpact < 0)
{
// shouldn't happen but
iImpact = 0;
}
iDamage = BulletImpact( pFirer, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial );
// accidentally shot
pTarget->flags.fIntendedTarget = FALSE;
}
if ( AmmoTypes[ubAmmoType].monsterSpit )
{
if (bHeadSlot != NO_SLOT)
{
pTarget->inv[ bHeadSlot ][0]->data.objectStatus -= (INT8) ( (iImpact / 2) + Random( (iImpact / 2) ) );
if ( pTarget->inv[ bHeadSlot ][0]->data.objectStatus <= USABLE )
{
if ( pTarget->inv[ bHeadSlot ][0]->data.objectStatus <= 0 )
{
DeleteObj( &(pTarget->inv[ bHeadSlot ]) );
DirtyMercPanelInterface( pTarget, DIRTYLEVEL2 );
}
// say curse?
}
}
}
else if (ubHitLocation == AIM_SHOT_HEAD)
{
// bullet to the head may damage any head item
bHeadSlot = HEAD1POS + (INT8) Random( 2 );
if ( pTarget->inv[ bHeadSlot ].exists() == true )
{
pTarget->inv[ bHeadSlot ][0]->data.objectStatus -= (INT8) ( Random( iImpact / 2 ) );
if ( pTarget->inv[ bHeadSlot ][0]->data.objectStatus < 0 )
{
// just break it...
pTarget->inv[ bHeadSlot ][0]->data.objectStatus = 1;
}
}
}
// check to see if the guy is a friendly?..if so, up the number of times wounded
if( ( pTarget->bTeam == gbPlayerNum ) )
{
gMercProfiles[ pTarget->ubProfile ].usTimesWounded++;
}
// check to see if someone was accidentally hit when no target was specified by the player
if ( pFirer->bTeam == gbPlayerNum && pFirer->ubTargetID == NOBODY && pTarget->aiData.bNeutral )
{
if ( pTarget->ubCivilianGroup == KINGPIN_CIV_GROUP || pTarget->ubCivilianGroup == HICKS_CIV_GROUP )
{
// hicks and kingpin are touchy!
pFirer->ubTargetID = pTarget->ubID;
}
else if ( Random( 100 ) < 60 )
{
// get touchy
pFirer->ubTargetID = pTarget->ubID;
}
}
// Send event for getting hit
memset( &(SWeaponHit), 0, sizeof( SWeaponHit ) );
SWeaponHit.usSoldierID = pTarget->ubID;
SWeaponHit.uiUniqueId = pTarget->uiUniqueSoldierIdValue;
SWeaponHit.usWeaponIndex = pFirer->usAttackingWeapon;
SWeaponHit.sDamage = (INT16) iDamage;
// breath loss is based on original impact of bullet
SWeaponHit.sBreathLoss = (INT16) ( (iImpact * APBPConstants[BP_GET_WOUNDED] * (pTarget->bBreathMax * 100 - pTarget->sBreathRed)) / 10000 );
SWeaponHit.usDirection = GetDirectionFromGridNo( pFirer->sGridNo, pTarget );
SWeaponHit.sXPos = (INT16)pTarget->dXPos;
SWeaponHit.sYPos = (INT16)pTarget->dYPos;
SWeaponHit.sZPos = 20;
SWeaponHit.sRange = sRange;
SWeaponHit.ubAttackerID = pFirer->ubID;
SWeaponHit.fHit = TRUE;
SWeaponHit.ubLocation = ubHitLocation;
if ( (pFirer->bDoBurst) && (ubSpecial == FIRE_WEAPON_NO_SPECIAL) )
{
// the animation required by the bullet hit (head explosion etc) overrides the
// hit-by-a-burst animation
ubSpecial = FIRE_WEAPON_BURST_SPECIAL;
}
SWeaponHit.ubSpecial = ubSpecial;
// now check to see if the bullet goes THROUGH this person! (not vehicles)
if ( !(pTarget->flags.uiStatusFlags & SOLDIER_VEHICLE) && (AmmoTypes[ubAmmoType].canGoThrough) && !EXPLOSIVE_GUN( pFirer->usAttackingWeapon ) )
{
// if we do more damage than expected, then the bullet will be more likely
// to be lodged in the body
// if we do less than expected, then the bullet has been slowed and less
// likely to continue
// higher chance for bigger guns, because they'll go through the back armour
// reduce impact to match damage, if damage wasn't more than the impact
// due to good aim, etc.
if (iDamage < iImpact)
{
iImpact = iDamage;
}
uiChanceThrough = (UINT8) __max( 0, ( iImpact - 20 ) );
if (PreRandom( 100 ) < uiChanceThrough )
{
// bullet MAY go through
// adjust for bullet going through person
iImpact -= CalcBodyImpactReduction( ubAmmoType, ubHitLocation );
// adjust for other side of armour!
iImpact -= TotalArmourProtection( pFirer, pTarget, ubHitLocation, iImpact, ubAmmoType );
if (iImpact > 0)
{
pBullet->iImpact = (INT8) iImpact;
// bullet was NOT stopped
fStopped = FALSE;
}
}
}
if ( gTacticalStatus.ubCurrentTeam != OUR_TEAM && pTarget->bTeam == gbPlayerNum )
{
// someone has been hit so no close-call quotes
gTacticalStatus.fSomeoneHit = TRUE;
}
// handle hit! Handle damage before deleting the bullet though
//hayden
if(SWeaponHit.ubAttackerID < 20 ||(is_server && SWeaponHit.ubAttackerID < 120)|| !is_client ) // 124 last possible ai
{
if(is_client)
SWeaponHit.sDamage=(INT16)(SWeaponHit.sDamage * DAMAGE_MULTIPLIER); // adjust damage from external variable //hayden
WeaponHit( SWeaponHit.usSoldierID, SWeaponHit.usWeaponIndex, SWeaponHit.sDamage, SWeaponHit.sBreathLoss, SWeaponHit.usDirection, SWeaponHit.sXPos, SWeaponHit.sYPos, SWeaponHit.sZPos, SWeaponHit.sRange, SWeaponHit.ubAttackerID, SWeaponHit.fHit, SWeaponHit.ubSpecial, SWeaponHit.ubLocation );
if(is_server || (is_client && SWeaponHit.ubAttackerID <20) )
{
SWeaponHit.fStopped=fStopped;
SWeaponHit.iBullet=pBullet->iBullet;
send_hit( &SWeaponHit );
}
}
if (fStopped)
{
RemoveBullet( pBullet->iBullet );
}
else
{
// ATE: I'm in enemy territory again, evil CC's world :)
// This looks like the place I should add code to spew blood on the ground
// The algorithm I'm going to use is given the current gridno of bullet,
// get a new gridno based on direction it was moving. Check to see if we're not
// going through walls, etc by testing for a path, unless on the roof, in which case it would always
// be legal, but the bLevel May change...
sNewGridNo = NewGridNo( (INT16)pBullet->sGridNo, DirectionInc( gOppositeDirection[ SWeaponHit.usDirection ] ) );
bSpewBloodLevel = MercPtrs[ SWeaponHit.usSoldierID ]->pathing.bLevel;
fCanSpewBlood = TRUE;
// If on anything other than bLevel of 0, we can pretty much freely spew blood
if ( bSpewBloodLevel == 0 )
{
if ( gubWorldMovementCosts[ sNewGridNo ][ gOppositeDirection[ SWeaponHit.usDirection ] ][ 0 ] >= TRAVELCOST_BLOCKED )
{
fCanSpewBlood = FALSE;
}
}
else
{
// If a roof does not exist here, make level = 0
if ( !IsRoofPresentAtGridno( sNewGridNo ) )
{
bSpewBloodLevel = 0;
}
}
if ( fCanSpewBlood )
{
// Drop blood dude!
InternalDropBlood( sNewGridNo, bSpewBloodLevel, 0, (UINT8)(MAXBLOODQUANTITY), 1 );
}
}
return( fStopped );
}
void BulletHitStructure( BULLET * pBullet, UINT16 usStructureID, INT32 iImpact, SOLDIERTYPE * pFirer, FIXEDPT qCurrX, FIXEDPT qCurrY, FIXEDPT qCurrZ, BOOLEAN fStopped )
{
EV_S_STRUCTUREHIT SStructureHit;
SStructureHit.sXPos = (INT16) FIXEDPT_TO_INT32( qCurrX + FloatToFixed( 0.5f ) ); // + 0.5);
SStructureHit.sYPos = (INT16) FIXEDPT_TO_INT32( qCurrY + FloatToFixed( 0.5f ) ); // (dCurrY + 0.5);
SStructureHit.sZPos = CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16) FIXEDPT_TO_INT32( qCurrZ + FloatToFixed( 0.5f )) );// dCurrZ + 0.5) );
SStructureHit.usWeaponIndex = pFirer->usAttackingWeapon;
SStructureHit.bWeaponStatus = pBullet->ubItemStatus;
SStructureHit.ubAttackerID = pFirer->ubID;
SStructureHit.usStructureID = usStructureID;
SStructureHit.iImpact = iImpact;
SStructureHit.iBullet = pBullet->iBullet;
if (is_networked)
SStructureHit.fStopped = fStopped;
//hayden
if(is_client)
send_hitstruct(&SStructureHit);
StructureHit( SStructureHit.iBullet, SStructureHit.usWeaponIndex, SStructureHit.bWeaponStatus, SStructureHit.ubAttackerID, SStructureHit.sXPos, SStructureHit.sYPos, SStructureHit.sZPos, SStructureHit.usStructureID, SStructureHit.iImpact, fStopped );
}
void BulletHitWindow( BULLET *pBullet, INT16 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth )
{
if (is_networked)
{
EV_S_WINDOWHIT SWindowHit;
SWindowHit.sGridNo = sGridNo;
SWindowHit.usStructureID = usStructureID;
SWindowHit.fBlowWindowSouth = fBlowWindowSouth;
SWindowHit.fLargeForce = FALSE;
SWindowHit.iBullet = pBullet->iBullet;
SWindowHit.ubAttackerID=pBullet->pFirer->ubID;
//hayden
if(is_client)
send_hitwindow(&SWindowHit);
}
WindowHit( sGridNo, usStructureID, fBlowWindowSouth, FALSE );
}
void BulletMissed( BULLET *pBullet, SOLDIERTYPE * pFirer )
{
if (is_networked)
{
EV_S_MISS SMiss;
SMiss.iBullet=pBullet->iBullet;
SMiss.ubAttackerID=pFirer->ubID;
//hayden
if(is_client)
send_miss(&SMiss);
}
ShotMiss( pFirer->ubID, pBullet->iBullet );
}
UINT32 ChanceOfBulletHittingStructure( INT32 iDistance, INT32 iDistanceToTarget, INT16 sHitBy )
{
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ChanceOfBulletHittingStructure"));
INT32 iCloseToCoverPenalty;
if (iDistance / CELL_X_SIZE > MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE)
{
return( MAX_CHANCE_OF_HITTING_STRUCTURE );
}
else
{
iCloseToCoverPenalty = iDistance / 5 - (iDistanceToTarget - iDistance);
if (iCloseToCoverPenalty < 0)
{
iCloseToCoverPenalty = 0;
}
if (sHitBy < 0)
{
// add 20% to distance so that misses hit nearer obstacles a bit more
iDistance += iDistance / 5;
}
if ( ( (iDistance + iCloseToCoverPenalty) / CELL_X_SIZE ) > MAX_DIST_FOR_LESS_THAN_MAX_CHANCE_TO_HIT_STRUCTURE )
{
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ChanceOfBulletHittingStructure done"));
return( MAX_CHANCE_OF_HITTING_STRUCTURE );
}
else
{
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ChanceOfBulletHittingStructure done"));
return( guiStructureHitChance[ (iDistance + iCloseToCoverPenalty) / CELL_X_SIZE ] );
}
}
}
INT32 StructureResistanceIncreasedByRange( INT32 iImpactReduction, INT32 iGunRange, INT32 iDistance )
{
return( iImpactReduction * ( 100 + PERCENT_BULLET_SLOWED_BY_RANGE * (iDistance - iGunRange) / iGunRange ) / 100 );
/*
if ( iDistance > iGunRange )
{
return( iImpactReduction * ( 100 + PERCENT_BULLET_SLOWED_BY_RANGE * (iDistance - iGunRange) / iGunRange ) / 100 );
}
else
{
return( iImpactReduction );
}
*/
}
INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN * pfHit )
{
DOOR *pDoor;
INT16 sLockDamage;
// returns remaining impact amount
INT32 iCurrImpact;
INT32 iImpactReduction;
*pfHit = FALSE;
if (pBullet->usFlags & BULLET_FLAG_KNIFE || pBullet->usFlags & BULLET_FLAG_MISSILE || pBullet->usFlags & BULLET_FLAG_TANK_CANNON || pBullet->usFlags & BULLET_FLAG_FLAME )
{
// stops!
*pfHit = TRUE;
return( 0 );
}
//else if ( pBullet->usFlags & BULLET_FLAG_SMALL_MISSILE )
//{
// stops if using HE ammo
else if ( AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].highExplosive && !AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].antiTank )
{
*pfHit = TRUE;
return( 0 );
}
// }
// ATE: Alrighty, check for shooting door locks...
// First check this is a type of struct that can handle locks...
if ( pStructure->fFlags & ( STRUCTURE_DOOR | STRUCTURE_OPENABLE ) && PythSpacesAway( (INT16) pBullet->sTargetGridNo, pStructure->sGridNo ) <= 2 )
{
// lookup lock table to see if we have a lock,
// and then remove lock if enough damage done....
pDoor = FindDoorInfoAtGridNo( pBullet->sGridNo );
// Does it have a lock?
INT16 lockBustingPower = AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].lockBustingPower;
// WANNE: bugfix: No door returned, so the game crashes!
if (pDoor)
{
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Door info: damage = %d, pick difficulty = %d, smash difficulty = %d, lockbuster power = %d",pDoor->bLockDamage,LockTable[ pDoor->ubLockID ].ubPickDifficulty,LockTable[ pDoor->ubLockID ].ubSmashDifficulty,lockBustingPower) );
if ( pDoor && (( LockTable[ pDoor->ubLockID ].ubPickDifficulty < 50 && LockTable[ pDoor->ubLockID ].ubSmashDifficulty < 70 ) || lockBustingPower*2 >= LockTable[ pDoor->ubLockID ].ubSmashDifficulty ) )
{
// Yup.....
// Chance that it hit the lock....
if ( PreRandom( 2 ) == 0 || lockBustingPower > 0 )
{
// Adjust damage-- CC adjust this based on gun type, etc.....
//sLockDamage = (INT16)( 35 + Random( 35 ) );
sLockDamage = (INT16) (pBullet->iImpact - pBullet->iImpactReduction );
sLockDamage += (INT16) PreRandom( sLockDamage );
sLockDamage += lockBustingPower;
sLockDamage = min(sLockDamage,127);
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, TacticalStr[ LOCK_HAS_BEEN_HIT ] );
//Madd: catch the overflow
if ( sLockDamage + pDoor->bLockDamage > 127 )
pDoor->bLockDamage = 127;
else
pDoor->bLockDamage+= sLockDamage;
// Check if it has been shot!
if ( pDoor->bLockDamage > LockTable[ pDoor->ubLockID ].ubSmashDifficulty || sLockDamage > LockTable[ pDoor->ubLockID ].ubSmashDifficulty )
{
// Display message!
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, TacticalStr[ LOCK_HAS_BEEN_DESTROYED ] );
// succeeded! door can never be locked again, so remove from door list...
RemoveDoorInfoFromTable( pDoor->sGridNo );
// MARKSMANSHIP GAIN (marksPts): Opened/Damaged a door
StatChange( pBullet->pFirer, MARKAMT, 10, FALSE );
}
}
}
}
}
// okay, this seems pretty weird, so here's the comment to explain it:
// iImpactReduction is the reduction in impact due to the structure
// pBullet->iImpactReduction is the accumulated reduction in impact
// for all bullets encountered thus far
// iCurrImpact is the original impact value of the bullet reduced due to
// range. To avoid problems involving multiple multiplication
// ( (1 - X) * (1 - Y) != (1 - X - Y) ! ), this is calculated from
// scratch at each collision with an obstacle
// reduction due to range is 25% per "max range"
if (PreRandom( 100 ) < pStructure->pDBStructureRef->pDBStructure->ubDensity)
{
iCurrImpact = pBullet->iImpact;
//iCurrImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
iImpactReduction = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ];
iImpactReduction = StructureResistanceIncreasedByRange( iImpactReduction, pBullet->iRange, pBullet->iLoop );
iImpactReduction = (INT32) (iImpactReduction * AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionMultiplier / max(1,AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionDivisor));
//switch (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType)
//{
// case AMMO_HP:
// iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_HP( iImpactReduction );
// break;
// case AMMO_AP:
// case AMMO_HEAT:
// iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_AP( iImpactReduction );
// break;
// case AMMO_SUPER_AP:
// iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_SAP( iImpactReduction );
// break;
// default:
// break;
//}
pBullet->iImpactReduction += iImpactReduction;
// really weak stuff like grass should never *stop* a bullet, maybe slow it though
if ( pStructure->pDBStructureRef->pDBStructure->ubArmour == MATERIAL_LIGHT_VEGETATION )
{
// just return a +ve value to indicate the bullet wasn't stopped
*pfHit = FALSE;
return( 1 );
}
*pfHit = TRUE;
return( iCurrImpact - pBullet->iImpactReduction );
}
else
{
// just return a +ve value to indicate the bullet wasn't stopped
*pfHit = FALSE;
return( 1 );
}
}
INT32 CTGTHandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure )
{
// returns reduction in impact for summing in CTGT
INT32 iCurrImpact;
INT32 iImpactReduction;
if (pBullet->usFlags & BULLET_FLAG_KNIFE || pBullet->usFlags & BULLET_FLAG_MISSILE || pBullet->usFlags & BULLET_FLAG_FLAME || pBullet->usFlags & BULLET_FLAG_TANK_CANNON )
{
// knife/rocket stops when it hits anything, and people block completely
return( pBullet->iImpact );
}
//else if ( pBullet->usFlags & BULLET_FLAG_SMALL_MISSILE )
//{
// stops if using HE ammo
else if ( AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].highExplosive && !AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].antiTank )
{
return( pBullet->iImpact );
}
// }
else if ( pStructure->fFlags & STRUCTURE_PERSON )
{
if ( pStructure->usStructureID != pBullet->ubFirerID && pStructure->usStructureID != pBullet->ubTargetID )
{
}
}
// okay, this seems pretty weird, so here's the comment to explain it:
// iImpactReduction is the reduction in impact due to the structure
// pBullet->iImpactReduction is the accumulated reduction in impact
// for all bullets encountered thus far
// iCurrImpact is the original impact value of the bullet reduced due to
// range. To avoid problems involving multiple multiplication
// ( (1 - X) * (1 - Y) != (1 - X - Y) ! ), this is calculated from
// scratch at each collision with an obstacle
// reduction due to range is 25% per "max range"
//iCurrImpact = BulletImpactReducedByRange( pBullet->iImpact, pBullet->iLoop, pBullet->iRange );
iCurrImpact = pBullet->iImpact;
// multiply impact reduction by 100 to retain fractions for a bit...
iImpactReduction = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ] * pStructure->pDBStructureRef->pDBStructure->ubDensity / 100;
iImpactReduction = StructureResistanceIncreasedByRange( iImpactReduction, pBullet->iRange, pBullet->iLoop );
iImpactReduction = (INT32)(iImpactReduction * AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionMultiplier / max(1,AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionDivisor));
//switch (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType)
//{
// case AMMO_HP:
// iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_HP( iImpactReduction );
// break;
// case AMMO_AP:
// iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_AP( iImpactReduction );
// break;
// case AMMO_SUPER_AP:
// iImpactReduction = AMMO_STRUCTURE_ADJUSTMENT_SAP( iImpactReduction );
// break;
// default:
// break;
//}
return( iImpactReduction );
}
UINT8 CalcChanceToGetThrough( BULLET * pBullet )
{
FIXEDPT qLandHeight;
INT32 iCurrAboveLevelZ;
INT32 iCurrCubesAboveLevelZ;
INT16 sDesiredLevel;
INT32 iOldTileX;
INT32 iOldTileY;
INT32 iOldCubesZ;
MAP_ELEMENT * pMapElement;
STRUCTURE * pStructure;
STRUCTURE * pRoofStructure = NULL;
FIXEDPT qLastZ;
BOOLEAN fIntended;
INT8 bOldLOSIndexX;
INT8 bOldLOSIndexY;
INT32 iChanceToGetThrough = 100;
FIXEDPT qDistToTravelX;
FIXEDPT qDistToTravelY;
INT32 iStepsToTravelX;
INT32 iStepsToTravelY;
INT32 iStepsToTravel;
INT32 iNumLocalStructures;
INT32 iStructureLoop;
UINT32 uiChanceOfHit;
INT32 iGridNo;
INT32 iTotalStructureImpact;
BOOLEAN fResolveHit;
FIXEDPT qWallHeight;
FIXEDPT qWindowBottomHeight;
FIXEDPT qWindowTopHeight;
DebugLOS( "Starting CalcChanceToGetThrough" );
do
{
// check a particular tile
// retrieve values from world for this particular tile
iGridNo = pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS;
DebugLOS( String( "CTGT now at %ld", iGridNo ) );
pMapElement = &(gpWorldLevelData[ iGridNo ] );
qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) );
qWallHeight = gqStandardWallHeight + qLandHeight;
qWindowBottomHeight = gqStandardWindowBottomHeight + qLandHeight;
qWindowTopHeight = gqStandardWindowTopHeight + qLandHeight;
// Assemble list of structures we might hit!
iNumLocalStructures = 0;
pStructure = pMapElement->pStructureHead;
// calculate chance of hitting each structure
uiChanceOfHit = ChanceOfBulletHittingStructure( pBullet->iLoop, pBullet->iDistanceLimit, 0 );
// reset roof structure pointer each tile
pRoofStructure = NULL;
if (iGridNo == (INT32) pBullet->sTargetGridNo)
{
fIntended = TRUE;
// if in the same tile as our destination, we WANT to hit the structure!
uiChanceOfHit = 100;
}
else
{
fIntended = FALSE;
}
iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight );
if (iCurrAboveLevelZ < 0)
{
// ground is in the way!
DebugLOS(String("CalcChanceToGetThrough done ground in way #1"));
return( 0 );
}
iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
DebugLOS(String("CalcChanceToGetThrough: while pStructure"));
while( pStructure )
{
if (pStructure->fFlags & ALWAYS_CONSIDER_HIT)
{
// ALWAYS add walls
gpLocalStructure[iNumLocalStructures] = pStructure;
// fence is special
//(iCurrCubesAboveLevelZ <= iStartCubesAboveLevelZ)
if ( pStructure->fFlags & STRUCTURE_ANYFENCE )
{
if (pStructure->pDBStructureRef->pDBStructure->ubDensity < 100)
{
guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
}
else if ( (pBullet->iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bStartCubesAboveLevelZ) && (pBullet->bEndCubesAboveLevelZ >= iCurrCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) )
{
guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
}
else if ( (pBullet->iLoop + pBullet->iDistanceLimit / 50 >= pBullet->iDistanceLimit - CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bEndCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) )
{
guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
}
else
{
guiLocalStructureCTH[iNumLocalStructures] = 100;
}
}
else
{
guiLocalStructureCTH[iNumLocalStructures] = 100;
}
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
else if (pStructure->fFlags & STRUCTURE_ROOF)
{
// only consider roofs if the flag is set; don't add them to the array since they
// are a special case
if (pBullet->fCheckForRoof)
{
pRoofStructure = pStructure;
if ( pRoofStructure )
{
qLastZ = pBullet->qCurrZ - pBullet->qIncrZ;
// if just on going to next tile we cross boundary, then roof stops bullet here!
if ( (qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight))
{
// hit a roof
DebugLOS(String("ChanceChanceToGetThrough done hit roof #1" ));
return( 0 );
}
}
}
}
else if (pStructure->fFlags & STRUCTURE_PERSON)
{
if ( (pStructure->usStructureID != pBullet->ubFirerID) && (pStructure->usStructureID != pBullet->ubTargetID ) )
{
// ignore intended target since we will get closure upon reaching the center
// of the destination tile
// ignore intervening target if not visible; PCs are always visible so AI will never skip them on that
// basis
if ( !fIntended && (MercPtrs[ pStructure->usStructureID ]->bVisible == TRUE) )
{
// in actually moving the bullet, we consider only count friends as targets if the bullet is unaimed
// (buckshot), if they are the intended target, or beyond the range of automatic friendly fire hits
// OR a 1 in 30 chance occurs
if (gAnimControl[ MercPtrs[pStructure->usStructureID]->usAnimState ].ubEndHeight == ANIM_STAND &&
( (pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS) ||
(!pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS_UNAIMED)
)
)
{
// could hit this person!
gpLocalStructure[iNumLocalStructures] = pStructure;
// CJC commented this out because of tank trying to shoot through another tank
//guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
guiLocalStructureCTH[iNumLocalStructures] = 100;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
else
{
// minimal chance of hitting this person
gpLocalStructure[iNumLocalStructures] = pStructure;
guiLocalStructureCTH[iNumLocalStructures] = MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
}
}
}
else if ( pStructure->fFlags & STRUCTURE_CORPSE )
{
if ( iGridNo == (INT32) pBullet->sTargetGridNo || (pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles >= 10) )
{
// could hit this corpse!
// but we should ignore the corpse if there is someone standing there
if ( FindStructure( (INT16) iGridNo, STRUCTURE_PERSON ) == NULL )
{
gpLocalStructure[iNumLocalStructures] = pStructure;
iNumLocalStructures++;
}
}
}
else
{
if ( pBullet->iLoop > CLOSE_TO_FIRER && !fIntended )
{
// could hit it
gpLocalStructure[iNumLocalStructures] = pStructure;
guiLocalStructureCTH[iNumLocalStructures] = uiChanceOfHit;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
}
pStructure = pStructure->pNext;
}
// record old tile location for loop purposes
iOldTileX = pBullet->iCurrTileX;
iOldTileY = pBullet->iCurrTileY;
DebugLOS(String("CalcChanceToGetThrough: checking for location within the tile"));
do
{
// check a particular location within the tile
// check for collision with the ground
iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight );
if (iCurrAboveLevelZ < 0)
{
// ground is in the way!
DebugLOS(String("ChanceOfBulletHittingStructure done ground in way #2"));
return( 0 );
}
// check for the existence of structures
pStructure = pMapElement->pStructureHead;
if (pStructure == NULL)
{ // no structures in this tile, and THAT INCLUDES ROOFS AND PEOPLE! :-)
// new system; figure out how many steps until we cross the next edge
// and then fast forward that many steps.
iOldTileX = pBullet->iCurrTileX;
iOldTileY = pBullet->iCurrTileY;
iOldCubesZ = pBullet->iCurrCubesZ;
if (pBullet->qIncrX > 0)
{
qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ));
iStepsToTravelX = qDistToTravelX / pBullet->qIncrX;
}
else if (pBullet->qIncrX < 0)
{
qDistToTravelX = pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE );
iStepsToTravelX = qDistToTravelX / (-pBullet->qIncrX);
}
else
{
// make sure we don't consider X a limit :-)
iStepsToTravelX = 1000000;
}
if (pBullet->qIncrY > 0)
{
qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ));
iStepsToTravelY = qDistToTravelY / pBullet->qIncrY;
}
else if (pBullet->qIncrY < 0)
{
qDistToTravelY = pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE );
iStepsToTravelY = qDistToTravelY / (-pBullet->qIncrY);
}
else
{
// make sure we don't consider Y a limit :-)
iStepsToTravelY = 1000000;
}
// add 1 to the # of steps to travel to go INTO the next tile
iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1;
pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel;
pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel;
pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel;
pBullet->iLoop += iStepsToTravel;
// check for ground collision
if (pBullet->qCurrZ < qLandHeight && pBullet->iLoop < pBullet->iDistanceLimit)
{
// ground is in the way!
DebugLOS(String("ChanceChanceToGetThrough done ground in way #3" ));
return( 0 );
}
// figure out the new tile location
pBullet->iCurrTileX = FIXEDPT_TO_TILE_NUM( pBullet->qCurrX );
pBullet->iCurrTileY = FIXEDPT_TO_TILE_NUM( pBullet->qCurrY );
pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX );
pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY );
DebugLOS( String( " CTGT at %ld %ld after traversing empty tile", pBullet->bLOSIndexX, pBullet->bLOSIndexY ) );
}
else
{
// there are structures in this tile
iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
// figure out the LOS cube level of the current point
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
{
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
{
// check objects on the ground
sDesiredLevel = STRUCTURE_ON_GROUND;
}
else
{
// check objects on roofs
sDesiredLevel = STRUCTURE_ON_ROOF;
iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
}
// check structures for collision
for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++)
{
pStructure = gpLocalStructure[iStructureLoop];
if (pStructure && pStructure->sCubeOffset == sDesiredLevel)
{
if (((*(pStructure->pShape))[pBullet->bLOSIndexX][pBullet->bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0)
{
if (pStructure->fFlags & STRUCTURE_PERSON)
{
// hit someone?
if (fIntended)
{ // gotcha! ... return chance to get through
iChanceToGetThrough = iChanceToGetThrough * (pBullet->iImpact - pBullet->iImpactReduction) / pBullet->iImpact;
DebugLOS(String("ChanceChanceToGetThrough done, hit intended, returning %d", iChanceToGetThrough ));
return( (UINT8) iChanceToGetThrough );
}
else
{
gubLocalStructureNumTimesHit[iStructureLoop]++;
}
}
else if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && pBullet->qCurrZ >= qWindowBottomHeight && pBullet->qCurrZ <= qWindowTopHeight)
{
fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
if (fResolveHit)
{
// the bullet would keep on going! unless we're considering a knife...
if (pBullet->usFlags & BULLET_FLAG_KNIFE)
{
gubLocalStructureNumTimesHit[iStructureLoop]++;
}
}
}
else if (pBullet->iLoop > CLOSE_TO_FIRER || (pStructure->fFlags & ALWAYS_CONSIDER_HIT))
{
if (pStructure->fFlags & STRUCTURE_WALLSTUFF)
{
// possibly shooting at corner in which case we should let it pass
fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
}
else
{
fResolveHit = TRUE;
}
if (fResolveHit)
{
gubLocalStructureNumTimesHit[iStructureLoop]++;
}
}
}
}
}
}
// got past everything; go to next LOS location within
// tile, horizontally or vertically
bOldLOSIndexX = pBullet->bLOSIndexX;
bOldLOSIndexY = pBullet->bLOSIndexY;
iOldCubesZ = pBullet->iCurrCubesZ;
do
{
pBullet->qCurrX += pBullet->qIncrX;
pBullet->qCurrY += pBullet->qIncrY;
if (pRoofStructure)
{
qLastZ = pBullet->qCurrZ;
pBullet->qCurrZ += pBullet->qIncrZ;
if ( (qLastZ > qWallHeight && pBullet->qCurrZ < qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ > qWallHeight))
{
// hit roof!
//pBullet->iImpactReduction += CTGTHandleBulletStructureInteraction( pBullet, pRoofStructure );
//if (pBullet->iImpactReduction >= pBullet->iImpact)
{
DebugLOS(String("ChanceChanceToGetThrough done hit roof #2" ));
return( 0 );
}
}
}
else
{
pBullet->qCurrZ += pBullet->qIncrZ;
}
pBullet->iLoop++;
pBullet->bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrX ) % CELL_X_SIZE );
pBullet->bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrY ) % CELL_Y_SIZE );
pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
}
while( (pBullet->bLOSIndexX == bOldLOSIndexX) && (pBullet->bLOSIndexY == bOldLOSIndexY) && (pBullet->iCurrCubesZ == iOldCubesZ));
DebugLOS( String( " CTGT at %ld %ld %ld after moving in nonempty tile from %ld %ld %ld", pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->iCurrCubesZ, bOldLOSIndexX, bOldLOSIndexY, iOldCubesZ ) );
pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE;
pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE;
}
} while( (pBullet->iLoop < pBullet->iDistanceLimit) && (pBullet->iCurrTileX == iOldTileX) && (pBullet->iCurrTileY == iOldTileY));
if ( pBullet->iCurrTileX < 0 || pBullet->iCurrTileX >= WORLD_COLS || pBullet->iCurrTileY < 0 || pBullet->iCurrTileY >= WORLD_ROWS )
{
DebugLOS(String("CalcChanceToGetThrough done invalid position"));
return( 0 );
}
DebugLOS(String("CalcChanceToGetThrough check for bullet drop"));
pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY , pBullet->iCurrTileX );
if (pBullet->iLoop > pBullet->iRange * 2)
{
// beyond max effective range, bullet starts to drop!
// since we're doing an increment based on distance, not time, the
// decrement is scaled down depending on how fast the bullet is (effective range)
if ( pBullet->iRange <= 0 )
{
pBullet->iRange = 1;
}
pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2);
}
// end of the tile...
DebugLOS(String("CalcChanceToGetThrough end of tile"));
if (iNumLocalStructures > 0)
{
for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++)
{
// Calculate the total impact based on the number of points in the structure that were hit
if (gubLocalStructureNumTimesHit[iStructureLoop] > 0)
{
iTotalStructureImpact = CTGTHandleBulletStructureInteraction( pBullet, gpLocalStructure[iStructureLoop] ) * gubLocalStructureNumTimesHit[iStructureLoop];
// reduce the impact reduction of a structure tile to that of the bullet, since it can't do MORE than stop it.
iTotalStructureImpact = __min( iTotalStructureImpact, pBullet->iImpact );
// add to "impact reduction" based on strength of structure weighted by probability of hitting it
pBullet->iImpactReduction += (iTotalStructureImpact * guiLocalStructureCTH[iStructureLoop]) / 100;
}
}
if (pBullet->iImpactReduction >= pBullet->iImpact)
{
DebugLOS(String("CalcChanceToGetThrough done impact reduced"));
return( 0 );
}
}
} while( pBullet->iLoop < pBullet->iDistanceLimit );
// unless the distance is integral, after the loop there will be a
// fractional amount of distance remaining which is unchecked
// but we shouldn't(?) need to check it because the target is there!
DebugLOS(String("CalcChanceToGetThrough simple calc, %d * (%d - %d) / %d",
iChanceToGetThrough, pBullet->iImpact, pBullet->iImpactReduction, (max(pBullet->iImpact,1))));
// try simple chance to get through, ignoring range effects
iChanceToGetThrough = iChanceToGetThrough * (pBullet->iImpact - pBullet->iImpactReduction) / (max(pBullet->iImpact,1));
if (iChanceToGetThrough < 0)
{
iChanceToGetThrough = 0;
}
DebugLOS(String("CalcChanceToGetThrough done, returning %d", iChanceToGetThrough));
return( (UINT8) iChanceToGetThrough );
}
UINT8 SoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier )
{
FLOAT dEndZPos;
BOOLEAN fOk;
if (pStartSoldier == pEndSoldier)
{
return( 0 );
}
CHECKF( pStartSoldier );
CHECKF( pEndSoldier );
fOk = CalculateSoldierZPos( pEndSoldier, TARGET_POS, &dEndZPos );
if (!fOk)
{
return( FALSE );
}
// set startsoldier's target ID ... need an ID stored in case this
// is the AI calculating cover to a location where he might not be any more
pStartSoldier->ubCTGTTargetID = pEndSoldier->ubID;
return( ChanceToGetThrough( pStartSoldier, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos ) );
}
UINT8 SoldierToSoldierBodyPartChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier, UINT8 ubAimLocation )
{
// does like StS-CTGT but with a particular body part in mind
FLOAT dEndZPos;
BOOLEAN fOk;
UINT8 ubPosType;
if (pStartSoldier == pEndSoldier)
{
return( 0 );
}
CHECKF( pStartSoldier );
CHECKF( pEndSoldier );
switch( ubAimLocation )
{
case AIM_SHOT_HEAD:
ubPosType = HEAD_TARGET_POS;
break;
case AIM_SHOT_TORSO:
ubPosType = TORSO_TARGET_POS;
break;
case AIM_SHOT_LEGS:
ubPosType = LEGS_TARGET_POS;
break;
default:
ubPosType = TARGET_POS;
break;
}
fOk = CalculateSoldierZPos( pEndSoldier, ubPosType, &dEndZPos );
if (!fOk)
{
return( FALSE );
}
// set startsoldier's target ID ... need an ID stored in case this
// is the AI calculating cover to a location where he might not be any more
pStartSoldier->ubCTGTTargetID = pEndSoldier->ubID;
return( ChanceToGetThrough( pStartSoldier, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos ) );
}
UINT8 SoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bCubeLevel, UINT8 ubTargetID )
{
FLOAT dEndZPos;
INT16 sXPos;
INT16 sYPos;
INT8 bStructHeight;
SOLDIERTYPE * pEndSoldier;
if (pStartSoldier->sGridNo == sGridNo)
{
return( 0 );
}
CHECKF( pStartSoldier );
pEndSoldier = SimpleFindSoldier( sGridNo, bLevel );
if (pEndSoldier != NULL)
{
return( SoldierToSoldierChanceToGetThrough( pStartSoldier, pEndSoldier ) );
}
else
{
if (bCubeLevel)
{
// fire at the centre of the cube specified
dEndZPos = ( ( (FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE) ) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
}
else
{
bStructHeight = GetStructureTargetHeight( sGridNo, (BOOLEAN) (bLevel == 1) );
if (bStructHeight > 0)
{
// fire at the centre of the cube of the tallest structure
dEndZPos = ((FLOAT) (bStructHeight + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
}
else
{
// fire at 1 unit above the level of the ground
dEndZPos = (FLOAT) ((bLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1);
}
}
dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight );
ConvertGridNoToXY( sGridNo, &sXPos, &sYPos );
sXPos = sXPos * CELL_X_SIZE + (CELL_X_SIZE / 2);
sYPos = sYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2);
// set startsoldier's target ID ... need an ID stored in case this
// is the AI calculating cover to a location where he might not be any more
pStartSoldier->ubCTGTTargetID = ubTargetID;
return( ChanceToGetThrough( pStartSoldier, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos ) );
}
}
UINT8 AISoldierToSoldierChanceToGetThrough( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE * pEndSoldier )
{
// Like a standard CTGT algorithm BUT fakes the start soldier at standing height
FLOAT dEndZPos;
BOOLEAN fOk;
UINT8 ubChance;
UINT16 usTrueState;
if (pStartSoldier == pEndSoldier)
{
return( 0 );
}
CHECKF( pStartSoldier );
CHECKF( pEndSoldier );
fOk = CalculateSoldierZPos( pEndSoldier, TARGET_POS, &dEndZPos );
if (!fOk)
{
return( FALSE );
}
usTrueState = pStartSoldier->usAnimState;
pStartSoldier->usAnimState = STANDING;
// set startsoldier's target ID ... need an ID stored in case this
// is the AI calculating cover to a location where he might not be any more
pStartSoldier->ubCTGTTargetID = NOBODY;
ubChance = ChanceToGetThrough( pStartSoldier, (FLOAT) CenterX( pEndSoldier->sGridNo ), (FLOAT) CenterY( pEndSoldier->sGridNo ), dEndZPos );
pStartSoldier->usAnimState = usTrueState;
return( ubChance );
}
UINT8 AISoldierToLocationChanceToGetThrough( SOLDIERTYPE * pStartSoldier, INT16 sGridNo, INT8 bLevel, INT8 bCubeLevel )
{
FLOAT dEndZPos;
INT16 sXPos;
INT16 sYPos;
INT8 bStructHeight;
SOLDIERTYPE * pEndSoldier;
UINT16 usTrueState;
UINT8 ubChance;
if (pStartSoldier->sGridNo == sGridNo)
{
return( 0 );
}
CHECKF( pStartSoldier );
pEndSoldier = SimpleFindSoldier( sGridNo, bLevel );
if (pEndSoldier != NULL)
{
return( AISoldierToSoldierChanceToGetThrough( pStartSoldier, pEndSoldier ) );
}
else
{
if (bCubeLevel)
{
// fire at the centre of the cube specified
dEndZPos = ( (FLOAT) (bCubeLevel + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
}
else
{
bStructHeight = GetStructureTargetHeight( sGridNo, (BOOLEAN) (bLevel == 1) );
if (bStructHeight > 0)
{
// fire at the centre of the cube of the tallest structure
dEndZPos = ((FLOAT) (bStructHeight + bLevel * PROFILE_Z_SIZE) - 0.5f) * HEIGHT_UNITS_PER_INDEX;
}
else
{
// fire at 1 unit above the level of the ground
dEndZPos = (FLOAT) ((bLevel * PROFILE_Z_SIZE) * HEIGHT_UNITS_PER_INDEX + 1);
}
}
dEndZPos += CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[sGridNo].sHeight );
ConvertGridNoToXY( sGridNo, &sXPos, &sYPos );
sXPos = sXPos * CELL_X_SIZE + (CELL_X_SIZE / 2);
sYPos = sYPos * CELL_Y_SIZE + (CELL_Y_SIZE / 2);
// set startsoldier's target ID ... need an ID stored in case this
// is the AI calculating cover to a location where he might not be any more
pStartSoldier->ubCTGTTargetID = NOBODY;
usTrueState = pStartSoldier->usAnimState;
pStartSoldier->usAnimState = STANDING;
ubChance = ChanceToGetThrough( pStartSoldier, (FLOAT) sXPos, (FLOAT) sYPos, dEndZPos );
pStartSoldier->usAnimState = usTrueState;
return( ubChance );
}
}
void CalculateFiringIncrements( DOUBLE ddHorizAngle, DOUBLE ddVerticAngle, DOUBLE dd2DDistance, BULLET * pBullet, DOUBLE * pddNewHorizAngle, DOUBLE * pddNewVerticAngle )
{
INT32 iMissedBy = - pBullet->sHitBy;
DOUBLE ddVerticPercentOfMiss;
DOUBLE ddAbsVerticAngle;
DOUBLE ddScrewupAdjustmentLimit;
UINT32 uiChanceOfMissAbove;
DOUBLE ddMinimumMiss;
DOUBLE ddMaximumMiss;
DOUBLE ddAmountOfMiss;
if (iMissedBy > 0)
{
ddVerticPercentOfMiss = PreRandom( 50 );
ddAbsVerticAngle = ddVerticAngle;
if (ddAbsVerticAngle < 0)
{
ddAbsVerticAngle *= -1.0;
}
// chance of shooting over target is 60 for horizontal shots, up to 80% for shots at 22.5 degrees,
// and then down again to 50% for shots at 45+%.
if (ddAbsVerticAngle < DEGREES_22_5)
{
uiChanceOfMissAbove = 60 + (INT32) (20 * (ddAbsVerticAngle) / DEGREES_22_5);
}
else if (ddAbsVerticAngle < DEGREES_45)
{
uiChanceOfMissAbove = 80 - (INT32) (30.0 * (ddAbsVerticAngle - DEGREES_22_5) / DEGREES_22_5);
}
else
{
uiChanceOfMissAbove = 50;
}
// figure out change in horizontal and vertical angle due to shooter screwup
// the more the screwup, the greater the angle;
// for the look of things, the further away, reduce the angle a bit.
ddScrewupAdjustmentLimit = (dd2DDistance / CELL_X_SIZE) / 200;
if (ddScrewupAdjustmentLimit > MAX_AIMING_SCREWUP / 2)
{
ddScrewupAdjustmentLimit = MAX_AIMING_SCREWUP / 2;
}
ddMaximumMiss = MAX_AIMING_SCREWUP - ddScrewupAdjustmentLimit;
// Want to make sure that not too many misses actually hit the target after all
// to miss a target at 1 tile is about 30 degrees off, at 5 tiles, 6 degrees off
// at 15 tiles, 2 degrees off. Thus 30 degrees divided by the # of tiles distance.
ddMinimumMiss = MIN_AIMING_SCREWUP / (dd2DDistance / CELL_X_SIZE );
if (ddMinimumMiss > ddMaximumMiss)
{
ddMinimumMiss = ddMaximumMiss;
}
ddAmountOfMiss = ( (ddMaximumMiss - ddMinimumMiss) * (DOUBLE) iMissedBy) / 100.0 + ddMinimumMiss;
// miss to the left or right
if (PreRandom( 2 ) )
{
ddHorizAngle += ddAmountOfMiss * (100.0 - ddVerticPercentOfMiss) / 100.0;
}
else
{
ddHorizAngle -= ddAmountOfMiss * (100.0 - ddVerticPercentOfMiss) / 100.0;
}
// miss up or down
if (PreRandom( 100 ) < uiChanceOfMissAbove)
{
ddVerticAngle += ddAmountOfMiss * ddVerticPercentOfMiss / 100.0;
}
else
{
ddVerticAngle -= ddAmountOfMiss * ddVerticPercentOfMiss / 100.0;
}
}
*pddNewHorizAngle = ddHorizAngle;
*pddNewVerticAngle = ddVerticAngle;
pBullet->qIncrX = FloatToFixed( (FLOAT) cos( ddHorizAngle ) );
pBullet->qIncrY = FloatToFixed( (FLOAT) sin( ddHorizAngle ) );
// this is the same as multiplying the X and Y increments by the projection of the line in
// 3-space onto the horizontal plane, without reducing the X/Y increments and thus slowing
// the LOS code
pBullet->qIncrZ = FloatToFixed( (FLOAT) ( sin( ddVerticAngle ) / sin( (PI/2) - ddVerticAngle ) * 2.56 ) );
}
//zilpin: pellet spread patterns externalized in XML
//New firing increments calculation, without any adjustment.
void CalculateFiringIncrementsSimple( DOUBLE ddHorizAngle, DOUBLE ddVerticAngle, BULLET * pBullet)
{
pBullet->qIncrX = FloatToFixed( (FLOAT) cos( ddHorizAngle ) );
pBullet->qIncrY = FloatToFixed( (FLOAT) sin( ddHorizAngle ) );
pBullet->qIncrZ = FloatToFixed( (FLOAT) ( sin( ddVerticAngle ) / sin( (PI/2) - ddVerticAngle ) * 2.56 ) );
}
INT8 FireBullet( SOLDIERTYPE * pFirer, BULLET * pBullet, BOOLEAN fFake )
{
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet"));
pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE;
pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE;
pBullet->bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrX ) % CELL_X_SIZE );
pBullet->bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrY ) % CELL_Y_SIZE );
pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
pBullet->iLoop = 1;
pBullet->pFirer = pFirer;
pBullet->iImpactReduction = 0;
pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY, pBullet->iCurrTileX );
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet: bullet info set"));
if (fFake)
{
pBullet->ubTargetID = pFirer->ubCTGTTargetID;
return( CalcChanceToGetThrough( pBullet ) );
}
else
{
pBullet->ubTargetID = pFirer->ubTargetID;
//if ( gGameSettings.fOptions[ TOPTION_HIDE_BULLETS ] )
//{
// pBullet->uiLastUpdate = 0;
// pBullet->ubTilesPerUpdate = 4;
//}
//else
//{
pBullet->uiLastUpdate = 0;
pBullet->ubTilesPerUpdate = 1;
//}
// increment shots fired if shooter has a merc profile
if( ( pFirer->ubProfile != NO_PROFILE ) && ( pFirer->bTeam == 0 ) )
{
// another shot fired
gMercProfiles[ pFirer->ubProfile ].usShotsFired++;
}
if ( Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE )
{
pBullet->usClockTicksPerUpdate = 30;
}
else
{
//afp-start //always a fast bullet
if ( pBullet->usFlags & ( BULLET_FLAG_CREATURE_SPIT | BULLET_FLAG_KNIFE | BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME /*| BULLET_FLAG_TRACER*/ ) )
pBullet->usClockTicksPerUpdate = (Weapon[ pFirer->usAttackingWeapon ].ubBulletSpeed + GetBulletSpeedBonus(&pFirer->inv[pFirer->ubAttackingHand]) ) / 10;
else
if (gGameExternalOptions.gbBulletTracer)
pBullet->usClockTicksPerUpdate = (Weapon[ pFirer->usAttackingWeapon ].ubBulletSpeed + GetBulletSpeedBonus(&pFirer->inv[pFirer->ubAttackingHand]) ) / 10;
else
pBullet->usClockTicksPerUpdate = 1;
//afp-end
}
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet: handle flags"));
HandleBulletSpecialFlags( pBullet->iBullet );
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet: move bullet"));
MoveBullet( pBullet->iBullet );
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBullet done"));
return( TRUE );
}
}
/*
DOUBLE CalculateVerticalAngle( SOLDIERTYPE * pFirer, SOLDIERTYPE * pTarget )
{
DOUBLE dStartZ, dEndZ;
CalculateSoldierZPos( pFirer, FIRING_POS, &dStartZ );
CalculateSoldierZPos( pTarget, TARGET_POS, &dEndZ );
dDeltaX = (FLOAT) CenterX( pTarget->sGridNo ) - (FLOAT) CenterX( pFirer->sGridNo );
dDeltaY = (FLOAT) CenterY( pTarget->sGridNo ) - (FLOAT) CenterY( pFirer->sGridNo );
dDeltaZ = dEndZ - dStartZ;
d2DDistance = Distance2D( dDeltaX, dDeltaY );
ddOrigHorizAngle = atan2( dDeltaY, dDeltaX );
}
*/
INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, UINT16 usHandItem, INT16 sHitBy, BOOLEAN fBuckshot, BOOLEAN fFake )
{
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,"FireBulletGivenTarget");
// fFake indicates that we should set things up for a call to ChanceToGetThrough
FLOAT dStartZ=0;
FLOAT d2DDistance=0;
FLOAT dDeltaX=0;
FLOAT dDeltaY=0;
FLOAT dDeltaZ=0;
FLOAT dStartX=0;
FLOAT dStartY=0;
DOUBLE ddOrigHorizAngle=0;
DOUBLE ddOrigVerticAngle=0;
DOUBLE ddHorizAngle=0;
DOUBLE ddVerticAngle=0;
DOUBLE ddAdjustedHorizAngle=0;
DOUBLE ddAdjustedVerticAngle=0;
DOUBLE ddDummyHorizAngle=0;
DOUBLE ddDummyVerticAngle=0;
BULLET * pBullet=NULL;
INT32 iBullet=0;
INT32 iDistance=0;
UINT8 ubLoop=0;
UINT8 ubShots=0;
UINT8 ubImpact=0;
INT8 bCTGT;
UINT8 ubSpreadIndex = 0;
UINT16 usBulletFlags = 0;
int n=0;
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBulletGivenTarget"));
CalculateSoldierZPos( pFirer, FIRING_POS, &dStartZ );
dStartX = (FLOAT) CenterX( pFirer->sGridNo );
dStartY = (FLOAT) CenterY( pFirer->sGridNo );
dDeltaX = dEndX - dStartX;
dDeltaY = dEndY - dStartY;
dDeltaZ = dEndZ - dStartZ;
//lal bugfix
if( dDeltaZ > 0 )
d2DDistance = Distance3D( dDeltaX, dDeltaY, dDeltaZ );
else
d2DDistance = Distance2D( dDeltaX, dDeltaY );
//d2DDistance = Distance2D( dDeltaX, dDeltaY );
iDistance = (INT32) d2DDistance;
if ( d2DDistance != iDistance )
{
iDistance += 1;
d2DDistance = (FLOAT) ( iDistance);
}
ddOrigHorizAngle = atan2( dDeltaY, dDeltaX );
ddOrigVerticAngle = atan2( dDeltaZ, (d2DDistance * 2.56f) );
ddAdjustedHorizAngle = ddOrigHorizAngle;
ddAdjustedVerticAngle = ddOrigVerticAngle;
ubShots = 1;
fTracer = FALSE;
// Check if we have spit as a weapon!
if ( Weapon[ usHandItem ].ubWeaponClass == MONSTERCLASS )
{
usBulletFlags |= BULLET_FLAG_CREATURE_SPIT;
}
else if ( Item[ usHandItem ].usItemClass == IC_THROWING_KNIFE )
{
usBulletFlags |= BULLET_FLAG_KNIFE;
}
else if ( Item[usHandItem].rocketlauncher )
{
usBulletFlags |= BULLET_FLAG_MISSILE;
}
else if ( Item[usHandItem].cannon )
{
usBulletFlags |= BULLET_FLAG_TANK_CANNON;
}
else if ( Item[usHandItem].rocketrifle )
{
usBulletFlags |= BULLET_FLAG_SMALL_MISSILE;
}
else if ( usHandItem == FLAMETHROWER )
{
usBulletFlags |= BULLET_FLAG_FLAME;
ubSpreadIndex = 2;
}
// HEADROCK HAM B2.5: Set tracer effect on/off for individual bullets in a Tracer Magazine, as part of the
// New Tracer System.
else if (gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0)
&& AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect )
{
UINT16 iBulletsLeft, iBulletsPerTracer;
iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer;
iBulletsLeft = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst;
if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1)
{
fTracer = TRUE;
}
else
{
fTracer = FALSE;
}
}
else if ( AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect && (pFirer->bDoBurst || gGameSettings.fOptions[ TOPTION_TRACERS_FOR_SINGLE_FIRE ]) )
{
//usBulletFlags |= BULLET_FLAG_TRACER;
fTracer = TRUE;
}
ubImpact =(UINT8) GetDamage(&pFirer->inv[pFirer->ubAttackingHand]);
//zilpin: pellet spread patterns externalized in XML
/* zilpin: The section below, including line comments, is the original adjustment made to multiple projectile stats.
Left in comments for reference, but the new handling is after it.
The fBuckshot flag is now ignored, so it can probably be taken out of the function definition.
// if (!fFake)
{
if (fBuckshot)
{
// shotgun pellets fire 9 bullets doing 1/4 damage each
if (!fFake)
{
ubShots = AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].numberOfBullets;
// but you can't really aim the damn things very well!
if (sHitBy > 0)
{
sHitBy = sHitBy / 2;
}
if ( IsDuckbill( &(pFirer->inv[pFirer->ubAttackingHand])) )
{
ubSpreadIndex = 1;
}
if (pFirer->ubTargetID != NOBODY)
{
MercPtrs[ pFirer->ubTargetID ]->bNumPelletsHitBy = 0;
}
usBulletFlags |= BULLET_FLAG_BUCKSHOT;
}
// ubImpact = AMMO_DAMAGE_ADJUSTMENT_BUCKSHOT( ubImpact );
ubImpact = (UINT8) (ubImpact * AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].multipleBulletDamageMultiplier / max(1,AmmoTypes[pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType].multipleBulletDamageDivisor) );
}
}
*/
//zilpin: Begin new code block for spread patterns, number of projectiles, impact adjustment, etc.
{
ObjectData *weapon = &(pFirer->inv[pFirer->ubAttackingHand][0]->data);
ubShots = AmmoTypes[ weapon->gun.ubGunAmmoType].numberOfBullets;
ubSpreadIndex = GetSpreadPattern( &pFirer->inv[pFirer->ubAttackingHand] );
if( ubShots>1 && !fFake )
{
fBuckshot = true;
usBulletFlags |= BULLET_FLAG_BUCKSHOT;
ubImpact = (UINT8) (ubImpact * AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageMultiplier / max(1,AmmoTypes[weapon->gun.ubGunAmmoType].multipleBulletDamageDivisor) );
if (pFirer->ubTargetID != NOBODY)
{
MercPtrs[ pFirer->ubTargetID ]->bNumPelletsHitBy = 0;
}
}
weapon=NULL;
}
//zilpin: End of new code block.
// GET BULLET
for (ubLoop = 0; ubLoop < ubShots; ubLoop++)
{
iBullet = CreateBullet( pFirer->ubID, fFake, usBulletFlags,usHandItem );
if (iBullet == -1)
{
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Failed to create bullet") );
return( FALSE );
}
pBullet = GetBulletPtr( iBullet );
pBullet->sHitBy = sHitBy;
if (dStartZ < WALL_HEIGHT_UNITS)
{
if (dEndZ > WALL_HEIGHT_UNITS)
{
pBullet->fCheckForRoof = TRUE;
}
else
{
pBullet->fCheckForRoof = FALSE;
}
}
else // dStartZ >= WALL_HEIGHT_UNITS; presumably >
{
if (dEndZ < WALL_HEIGHT_UNITS)
{
pBullet->fCheckForRoof = TRUE;
}
else
{
pBullet->fCheckForRoof = FALSE;
}
}
//zilpin: pellet spread patterns externalized in XML
//Now the first shot will use the pattern, as well.
//Single shot weapons will still be stuck with straight-ahead only, though.
//if ( ubLoop == 0 )
if( ubShots == 1 )
{
ddHorizAngle = ddOrigHorizAngle;
ddVerticAngle = ddOrigVerticAngle;
// first bullet, roll to hit...
if ( sHitBy >= 0 )
{
// calculate by hand (well, without angles) to match LOS
pBullet->qIncrX = FloatToFixed( dDeltaX / (FLOAT)iDistance );
pBullet->qIncrY = FloatToFixed( dDeltaY / (FLOAT)iDistance );
pBullet->qIncrZ = FloatToFixed( dDeltaZ / (FLOAT)iDistance );
ddAdjustedHorizAngle = ddHorizAngle;
ddAdjustedVerticAngle = ddVerticAngle;
}
else
{
CalculateFiringIncrements( ddHorizAngle, ddVerticAngle, d2DDistance, pBullet, &ddAdjustedHorizAngle, &ddAdjustedVerticAngle );
}
}
else if( ubShots > 1 )
{
//Calculate the deviation due to to-hit, but only for the first shot.
//The bullet is only used temporarily. It gets reset later.
//Should this use d3DDistance?
if( ubLoop<1 ){
CalculateFiringIncrements( ddOrigHorizAngle, ddOrigVerticAngle, d2DDistance, pBullet, &ddAdjustedHorizAngle, &ddAdjustedVerticAngle );
}
// temporarily set bullet's sHitBy value to 0 to get unadjusted angles
//Note: this no longer needs to be done since the CalculateFiringIncrementsSimple is being used below.
//pBullet->sHitBy = 0;
//Also prevent out-of-bounds overflows.
//Rotates through the array if more pellets than expected, and uses pattern index 0 if requested pattern does not exist.
if( gpSpreadPattern == NULL || giSpreadPatternCount<1 ){
//XML file missing, or empty, or error while loading. Use hard-coded defaults.
//This is from the original code.
if( ubSpreadIndex > 2 || ubSpreadIndex<0 ) ubSpreadIndex=0;
n = ubLoop % BUCKSHOT_SHOTS;
ddHorizAngle = ddAdjustedHorizAngle + ddShotgunSpread[ubSpreadIndex][n][0];
ddVerticAngle = ddAdjustedVerticAngle + ddShotgunSpread[ubSpreadIndex][n][1];
}else{
double d=0,r=0,xspread=0,yspread=0;
int n=0;
//Use spread patterns loaded from XML.
if( ubSpreadIndex >= giSpreadPatternCount || ubSpreadIndex < 0)
ubSpreadIndex=0;
xspread=gpSpreadPattern[ubSpreadIndex].xspread;
yspread=gpSpreadPattern[ubSpreadIndex].yspread;
//Only use randomized spread pattern if the random spread is defined AND each static angle already fired once.
if( ubLoop >= gpSpreadPattern[ubSpreadIndex].iCount && (xspread + yspread) ){
//Create random angle within range, positive and negative.
switch( gpSpreadPattern[ubSpreadIndex].method )
{
case SPREADPATTERNMETHOD_RECT: //Rectangle Method. (Simple)
//Applying a new random number to each angle results in a rectangular spread pattern, rather than an oval one.
ddHorizAngle = (double)rand() * 2 * xspread / RAND_MAX - xspread;
ddVerticAngle = (double)rand() * 2 * yspread / RAND_MAX - yspread;
break;
case SPREADPATTERNMETHOD_DIAMOND: //Diamond Method. (Kinda Simple)
//Angles are generated within a diamond shaped region.
//This is more natural than the rectangular pattern, but still not optimal.
//The first random number is in a range of 0 to (x+y).
d = (xspread+yspread) * (double)rand() / (double)RAND_MAX;
//The second random number determines the percentage of that value to use on x. The rest is spent on y.
r = (double)rand() / (double)RAND_MAX;
ddHorizAngle = r*d;
ddVerticAngle = (1-r)*d;
//Positive and negative are then randomly determined. Otherwise, everthing would always shoot to the high right (+,+).
n = rand()%4;
if(n&1) ddHorizAngle *= -1;
if(n&2) ddVerticAngle *= -1;
break;
case SPREADPATTERNMETHOD_ELLIPSE: //Ellipse Method.
//Angles are generated within an ellipse.
//This is getting close to true spread behaviour, and may be the preferred general purpose method.
//However, due to the distribution of random numbers for r, the pattern generated tends to have
//more pellets land along the axis, making the pattern look a bit like the Swiss cross sometimes.
//(It only becomes noticable in simulations I do in an external program. In-game it looks fine.)
//
//First, get our random range of -pi to pi. This could be recalculated for every use, but doesn't need to be.
r=(double)rand()*2*PI/RAND_MAX - PI;
//Which axis is our major axis?
if( xspread > yspread )
{
//The Ellipse.
// Any point on an Ellipse border line = (x,y)
// where x = m * cos(r) and y = n * sin(r)
// where m = major axis radius and n = minor axis radius and r = all values -pi <= r <= pi
//We use any random value between -pi and pi, but vary the radius to get points inside the ellipse as well.
ddHorizAngle = ( (double)rand()*xspread / RAND_MAX ) * cos(r);
ddVerticAngle = ( (double)rand()*yspread / RAND_MAX ) * sin(r);
}else{
//Reverse sine and cosine if our major axis is y.
ddHorizAngle = ( (double)rand()*xspread / RAND_MAX ) * sin(r);
ddVerticAngle = ( (double)rand()*yspread / RAND_MAX ) * cos(r);
}
break;
case -1: //Optimal Method. (Most realistic)
//Not yet implemented.
//Using the random distribution above, normal distrubution causes more pellets to end up toward the middle.
//This is generally not noticable, and in fact feels more natural to some people (not zilpin),
//but is not _even_ distribution.
//In addition, since it is random, it is possible for all of the pellets to vear off center,
//resulting in rare unnatural freak shots, which would never occur in a real shotgun.
//This may add to gameplay, but it may bother some people (like zilpin).
//Should anyone devise a way to adjust the ellipse distribution to account for this, here is where to put it.
//This would probably be done by adjusting the random value of 'r' above.
//Contact zilpin for real life shotgun spread pattern data, if you happen to be one of those mathematicians
//who can extrapolate a succinct algorithm from raw data. But keep in mind that there's not much data to go on.
//
//Also note that in the real world, shot spread tends to take the shape of a sagging funnel, rather than a cone.
//That is to say, the path of each pellet follows a mild curve, not a straight line.
//I'm really not going to worry about that here. Ever.
break;
default:
//If an invalid method is set in the structure, no randomized pellets are shot.
//Note that there still may have been static angle pellets fired.
//This should never happen, since the XML loading function will set them to giSpreadPatternMethod_Default.
break;
}
}else if( gpSpreadPattern[ubSpreadIndex].iCount>0 ){
//Use static angle, if they exist.
n = ubLoop % gpSpreadPattern[ubSpreadIndex].iCount;
ddHorizAngle = gpSpreadPattern[ubSpreadIndex].x[n];
ddVerticAngle = gpSpreadPattern[ubSpreadIndex].y[n];
}else{
//No angles defined, so just fire straight.
ddHorizAngle = ddVerticAngle = 0.0;
}
//#ifdef JA2TESTVERSION
DOUBLE ddRawHorizAngle = ddHorizAngle;
DOUBLE ddRawVerticAngle = ddVerticAngle;
//#endif
//Adjust based on the to-hit deviation calculated when the first pellet was fired.
ddHorizAngle += ddAdjustedHorizAngle;
ddVerticAngle += ddAdjustedVerticAngle;
//Logging for debugging
//#ifdef JA2TESTVERSION
if(!fFake)
{
FILE *OutFile;
if ((OutFile = fopen("SpreadPatternLog.txt", "a+t")) != NULL)
{
//To easily cut-and-paste these values from the log into a C/C++ source file for later analysis
//Lots of reference debug info in a comment.
fprintf(OutFile, "{ % 9.8f , % 9.8f , % 9.8f , % 9.8f }, //DEBUG: merc %4d fired pellet %4d of %4d using method %4d %12s with SpreadPattern %4d %s\n",
ddRawHorizAngle, ddRawVerticAngle,
ddHorizAngle, ddVerticAngle,
pFirer->ubID, ubLoop, ubShots,
gpSpreadPattern[ubSpreadIndex].method, gSpreadPatternMethodNames[gpSpreadPattern[ubSpreadIndex].method],
ubSpreadIndex, gpSpreadPattern[ubSpreadIndex].Name,
NULL
);
fclose(OutFile);
}
}
//#endif
}
//Just calculate the increments the bullet will use, not any of the to-hit adjustments, because we already did.
CalculateFiringIncrementsSimple( ddHorizAngle, ddVerticAngle, pBullet );
}
pBullet->ddHorizAngle = ddHorizAngle;
if (ubLoop == 0 && pFirer->bDoBurst < 2)
{
pBullet->fAimed = TRUE;
}
else
{
// buckshot pellets after the first can hit friendlies even at close range
pBullet->fAimed = FALSE;
}
if ( pBullet->usFlags & BULLET_FLAG_KNIFE )
{
pBullet->ubItemStatus = pFirer->inv[pFirer->ubAttackingHand][0]->data.objectStatus;
}
// apply increments for first move
//zilpin: pellet spread patterns externalized in XML
//This is a bugfix for strange behavior when firing, such as the projectile hitting walls behind a merc.
//The bullet should start it's journey at the beginning, not down range.
//Commented out the qIncr adjustments.
pBullet->qCurrX = FloatToFixed( dStartX ) ; //+ pBullet->qIncrX;
pBullet->qCurrY = FloatToFixed( dStartY ) ; //+ pBullet->qIncrY;
pBullet->qCurrZ = FloatToFixed( dStartZ ) ; //+ pBullet->qIncrZ;
//zilpin: pellet spread patterns externalized in XML
//Also bugfix. I understand why this was added, but it causes problems.
/*
// NB we can only apply correction for leftovers if the bullet is going to hit
// because otherwise the increments are not right for the calculations!
if ( pBullet->sHitBy >= 0 )
{
pBullet->qCurrX += ( FloatToFixed( dDeltaX ) - pBullet->qIncrX * iDistance ) / 2;
pBullet->qCurrY += ( FloatToFixed( dDeltaY ) - pBullet->qIncrY * iDistance ) / 2;
pBullet->qCurrZ += ( FloatToFixed( dDeltaZ ) - pBullet->qIncrZ * iDistance ) / 2;
}
*/
pBullet->iImpact = ubImpact;
pBullet->iRange = GunRange( &(pFirer->inv[pFirer->ubAttackingHand]) );
pBullet->sTargetGridNo = ((INT32)dEndX) / CELL_X_SIZE + ((INT32)dEndY) / CELL_Y_SIZE * WORLD_COLS;
pBullet->bStartCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pFirer->sGridNo ].sHeight ) );
pBullet->bEndCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dEndZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pBullet->sTargetGridNo ].sHeight ) );
// this distance limit only applies in a "hard" sense to fake bullets for chance-to-get-through,
// but is used for determining structure hits by the regular code
pBullet->iDistanceLimit = iDistance;
// HEADROCK HAM BETA2.5: New method for signifying whether a bullet is a tracer or not, using an individual
// bullet structure flag. Hehehehe, I think this is kind of reverting to old code, isn't it?
if (gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0)
&& AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect )
{
UINT16 iBulletsLeft, iBulletsPerTracer;
iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer;
iBulletsLeft = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst;
// Is this specific bullet a tracer? - based on how many tracers there are per regular bullets in
// a tracer magazine (INI-settable).
if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1)
{
pBullet->fTracer = TRUE;
}
else
{
pBullet->fTracer = FALSE;
}
}
if (fFake)
{
bCTGT = FireBullet( pFirer, pBullet, TRUE );
RemoveBullet( iBullet );
return( bCTGT );
}
else
{
// if (ubLoop + 1 > pFirer->bBulletsLeft)
// {
// // this is an error!!
// ubLoop = ubLoop;
// }
//hayden
if(is_client)send_bullet( pBullet, usHandItem );
FireBullet( pFirer, pBullet, FALSE );
}
}
//DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBulletGivenTargetDone"));
return( TRUE );
}
INT8 ChanceToGetThrough( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ )
{
if ( Item[pFirer->usAttackingWeapon].usItemClass == IC_GUN || Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE || Item[pFirer->usAttackingWeapon].rocketlauncher )
{
BOOLEAN fBuckShot = FALSE;
// if shotgun, shotgun would have to be in main hand
if ( pFirer->inv[ HANDPOS ].usItem == pFirer->usAttackingWeapon )
{
if ( AmmoTypes[pFirer->inv[ HANDPOS ][0]->data.gun.ubGunAmmoType].numberOfBullets > 1 )
{
fBuckShot = TRUE;
}
}
return( FireBulletGivenTarget( pFirer, dEndX, dEndY, dEndZ, pFirer->usAttackingWeapon, 0, fBuckShot, TRUE ) );
}
else
{
// fake it
//store the current item then replace it with a Glock 17
OBJECTTYPE oldItemInHand (pFirer->inv[ HANDPOS ]);
UINT16 oldAttackingWeapon = pFirer->usAttackingWeapon;
//GLOCK_17_ForUseWithLOS is init in InitializeJA2()
pFirer->inv[ HANDPOS ] = GLOCK_17_ForUseWithLOS;
pFirer->usAttackingWeapon = GLOCK_17;
INT8 retVal = FireBulletGivenTarget( pFirer, dEndX, dEndY, dEndZ, GLOCK_17, 0, FALSE, TRUE );
pFirer->inv[ HANDPOS ] = oldItemInHand;
pFirer->usAttackingWeapon = oldAttackingWeapon;
return retVal;
}
}
void MoveBullet( INT32 iBullet )
{
BULLET *pBullet;
FIXEDPT qLandHeight;
INT32 iCurrAboveLevelZ;
INT32 iCurrCubesAboveLevelZ;
INT16 sDesiredLevel;
INT32 iOldTileX;
INT32 iOldTileY;
INT32 iOldCubesZ;
MAP_ELEMENT * pMapElement;
STRUCTURE * pStructure;
STRUCTURE * pRoofStructure = NULL;
FIXEDPT qLastZ;
SOLDIERTYPE * pTarget;
UINT8 ubTargetID;
BOOLEAN fIntended;
BOOLEAN fStopped;
INT8 bOldLOSIndexX;
INT8 bOldLOSIndexY;
UINT32 uiTileInc = 0;
UINT32 uiTime;
INT8 bDir;
INT32 iGridNo, iAdjGridNo;
INT32 iRemainingImpact;
FIXEDPT qDistToTravelX;
FIXEDPT qDistToTravelY;
INT32 iStepsToTravelX;
INT32 iStepsToTravelY;
INT32 iStepsToTravel;
INT32 iNumLocalStructures;
INT32 iStructureLoop;
UINT32 uiChanceOfHit;
BOOLEAN fResolveHit;
INT32 i;
BOOLEAN fGoingOver = FALSE;
BOOLEAN fHitStructure;
FIXEDPT qWallHeight;
FIXEDPT qWindowBottomHeight;
FIXEDPT qWindowTopHeight;
pBullet = GetBulletPtr( iBullet );
// CHECK MIN TIME ELAPSED
uiTime = GetJA2Clock( );
if ( ( uiTime - pBullet->uiLastUpdate ) < pBullet->usClockTicksPerUpdate )
{
return;
}
pBullet->uiLastUpdate = uiTime;
do
{
// check a particular tile
// retrieve values from world for this particular tile
iGridNo = pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS;
if (!GridNoOnVisibleWorldTile( (INT16) iGridNo ) || (pBullet->iCurrCubesZ > PROFILE_Z_SIZE * 2 && FIXEDPT_TO_INT32( pBullet->qIncrZ ) > 0 ) )
{
// bullet outside of world!
// NB remove bullet only flags a bullet for deletion; we still have access to the
// information in the structure
RemoveBullet( pBullet->iBullet );
if(ENABLE_COLLISION)
BulletMissed( pBullet, pBullet->pFirer );//only if local origin
return;
}
pMapElement = &(gpWorldLevelData[ iGridNo ]);
qLandHeight = INT32_TO_FIXEDPT( CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight ) );
qWallHeight = gqStandardWallHeight + qLandHeight;
qWindowBottomHeight = gqStandardWindowBottomHeight + qLandHeight;
qWindowTopHeight = gqStandardWindowTopHeight + qLandHeight;
// calculate which level bullet is on for suppression and close call purposes
// figure out the LOS cube level of the current point
iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight) );
// figure out the level
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
{
// check objects on the ground
sDesiredLevel = 0;
}
else
{
// check objects on roofs
sDesiredLevel = 1;
}
// assemble list of structures we might hit!
iNumLocalStructures = 0;
pStructure = pMapElement->pStructureHead;
// calculate chance of hitting each structure
uiChanceOfHit = ChanceOfBulletHittingStructure( pBullet->iLoop, pBullet->iDistanceLimit, pBullet->sHitBy );
if (iGridNo == (INT32) pBullet->sTargetGridNo)
{
fIntended = TRUE;
// if in the same tile as our destination, we WANT to hit the structure!
if ( fIntended )
{
uiChanceOfHit = 100;
}
}
else
{
fIntended = FALSE;
}
//check for structures at target, and calc chance to hit
//hayden, disable any structure t avoid collision
if(ENABLE_COLLISION)
{
while( pStructure )
{
if (pStructure->fFlags & ALWAYS_CONSIDER_HIT)
{
// ALWAYS add walls
// fence is special
if ( pStructure->fFlags & STRUCTURE_ANYFENCE )
{
// If the density of the fence is less than 100%, or this is the top of the fence, then roll the dice
// NB cubes are 0 based, heights 1 based
if ( pStructure->pDBStructureRef->pDBStructure->ubDensity < 100 )
{
// requires roll
if ( PreRandom( 100 ) < uiChanceOfHit )
{
gpLocalStructure[iNumLocalStructures] = pStructure;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
}
else if ( (pBullet->iLoop <= CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bStartCubesAboveLevelZ) && (pBullet->bEndCubesAboveLevelZ >= iCurrCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) )
{
// near firer and at top of structure and at same level as bullet's start
// requires roll
if ( PreRandom( 100 ) < uiChanceOfHit )
{
gpLocalStructure[iNumLocalStructures] = pStructure;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
}
else if ( (pBullet->iLoop + pBullet->iDistanceLimit / 50 >= pBullet->iDistanceLimit - CLOSE_TO_FIRER) && (iCurrCubesAboveLevelZ <= pBullet->bEndCubesAboveLevelZ) && iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) )
{
// near target and at top of structure and at same level as bullet's end
// requires roll
if ( PreRandom( 100 ) < uiChanceOfHit )
{
gpLocalStructure[iNumLocalStructures] = pStructure;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
}
else
{
// always add
gpLocalStructure[iNumLocalStructures] = pStructure;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
/*
if ( !( (pStructure->pDBStructureRef->pDBStructure->ubDensity < 100 || iCurrCubesAboveLevelZ == (StructureHeight( pStructure ) - 1) ) ) && (PreRandom( 100 ) >= uiChanceOfHit) )
{
gpLocalStructure[iNumLocalStructures] = pStructure;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
*/
}
else
{
gpLocalStructure[iNumLocalStructures] = pStructure;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
}
else if (pStructure->fFlags & STRUCTURE_ROOF)
{
// only consider roofs if the flag is set; don't add them to the array since they
// are a special case
if (pBullet->fCheckForRoof)
{
pRoofStructure = pStructure;
qLastZ = pBullet->qCurrZ - pBullet->qIncrZ;
// if just on going to next tile we cross boundary, then roof stops bullet here!
if ( (qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight))
{
// hit a roof
StopBullet( pBullet->iBullet );
BulletHitStructure( pBullet, 0, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE );
return;
}
}
}
else if (pStructure->fFlags & STRUCTURE_PERSON)
{
if ( MercPtrs[ pStructure->usStructureID ] != pBullet->pFirer )
{
// in actually moving the bullet, we consider only count friends as targets if the bullet is unaimed
// (buckshot), if they are the intended target, or beyond the range of automatic friendly fire hits
// OR a 1 in 30 chance occurs
// ignore *intervening* target if not visible; PCs are always visible so AI will never skip them on that
// basis
if (fIntended)
{
// could hit this person!
gpLocalStructure[iNumLocalStructures] = pStructure;
iNumLocalStructures++;
}
else if ( pBullet->pFirer->flags.uiStatusFlags & SOLDIER_MONSTER )
{
// monsters firing will always accidentally hit people but never accidentally hit each other.
if ( !(MercPtrs[ pStructure->usStructureID ]->flags.uiStatusFlags & SOLDIER_MONSTER) )
{
gpLocalStructure[iNumLocalStructures] = pStructure;
iNumLocalStructures++;
}
}
else if ( MercPtrs[ pStructure->usStructureID ]->bVisible == TRUE &&
gAnimControl[ MercPtrs[pStructure->usStructureID]->usAnimState ].ubEndHeight == ANIM_STAND &&
( (pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS) ||
(!pBullet->fAimed && pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS_UNAIMED) ||
PreRandom( 100 ) < MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE
)
)
{
// could hit this person!
gpLocalStructure[iNumLocalStructures] = pStructure;
iNumLocalStructures++;
}
// this might be a close call
if ( MercPtrs[ pStructure->usStructureID ]->bTeam == gbPlayerNum && pBullet->pFirer->bTeam != gbPlayerNum && sDesiredLevel == MercPtrs[ pStructure->usStructureID ]->pathing.bLevel )
{
MercPtrs[ pStructure->usStructureID ]->flags.fCloseCall = TRUE;
}
if ( IS_MERC_BODY_TYPE( MercPtrs[pStructure->usStructureID] ) )
{
// apply suppression, regardless of friendly or enemy
// except if friendly, not within a few tiles of shooter
if ( MercPtrs[ pStructure->usStructureID ]->bSide != pBullet->pFirer->bSide || pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS )
{
// buckshot has only a 1 in 2 chance of applying a suppression point
if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) )
{
// bullet goes whizzing by this guy!
switch ( gAnimControl[ MercPtrs[pStructure->usStructureID]->usAnimState ].ubEndHeight )
{
case ANIM_PRONE:
// two 1/4 chances of avoiding suppression pt - one below
if (PreRandom( 4 ) == 0)
{
break;
}
// else fall through
case ANIM_CROUCH:
// 1/4 chance of avoiding suppression pt
if (PreRandom( 4 ) == 0)
{
break;
}
// else fall through
default:
MercPtrs[pStructure->usStructureID]->ubSuppressionPoints++;
MercPtrs[pStructure->usStructureID]->ubSuppressorID = pBullet->pFirer->ubID;
break;
}
}
}
}
}
}
else if ( pStructure->fFlags & STRUCTURE_CORPSE )
{
if ( iGridNo == (INT32) pBullet->sTargetGridNo || (pStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles >= 10) )
{
// could hit this corpse!
// but ignore if someone is here
if ( FindStructure( (INT16) iGridNo, STRUCTURE_PERSON ) == NULL )
{
gpLocalStructure[iNumLocalStructures] = pStructure;
iNumLocalStructures++;
}
}
}
else
{
if (pBullet->iLoop > CLOSE_TO_FIRER || ( fIntended ) )
{
// calculate chance of hitting structure
if (PreRandom( 100 ) < uiChanceOfHit)
{
// could hit it
gpLocalStructure[iNumLocalStructures] = pStructure;
gubLocalStructureNumTimesHit[iNumLocalStructures] = 0;
iNumLocalStructures++;
}
}
}
pStructure = pStructure->pNext;
}
}
// check to see if any soldiers are nearby; those soldiers
// have their near-miss value incremented
if (pMapElement->ubAdjacentSoldierCnt > 0)
{
// cube level now calculated above!
// figure out the LOS cube level of the current point
//iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight) );
// figure out what level to affect...
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
{
/*
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
{
// check objects on the ground
sDesiredLevel = 0;
}
else
{
// check objects on roofs
sDesiredLevel = 1;
}
*/
for( bDir = 0; bDir < NUM_WORLD_DIRECTIONS; bDir++)
{
iAdjGridNo = iGridNo + DirIncrementer[bDir];
if ( gubWorldMovementCosts[ iAdjGridNo ][ bDir ][ sDesiredLevel ] < TRAVELCOST_BLOCKED)
{
ubTargetID = WhoIsThere2( (INT16) iAdjGridNo, (INT8) sDesiredLevel );
if (ubTargetID != NOBODY)
{
pTarget = MercPtrs[ ubTargetID ];
if ( IS_MERC_BODY_TYPE( pTarget ) && pBullet->pFirer->bSide != pTarget->bSide )
{
if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) )
{
// bullet goes whizzing by this guy!
switch ( gAnimControl[ pTarget->usAnimState ].ubEndHeight )
{
case ANIM_PRONE:
// two 1/4 chances of avoiding suppression pt - one below
if (PreRandom( 4 ) == 0)
{
break;
}
// else fall through
case ANIM_CROUCH:
// 1/4 chance of avoiding suppression pt
if (PreRandom( 4 ) == 0)
{
break;
}
// else fall through
default:
pTarget->ubSuppressionPoints++;
pTarget->ubSuppressorID = pBullet->pFirer->ubID;
break;
}
}
/*
// this could be a close call
if ( pTarget->bTeam == gbPlayerNum && pBullet->pFirer->bTeam != gbPlayerNum )
{
pTarget->flags.fCloseCall = TRUE;
}
*/
}
}
}
}
}
}
// record old tile location for loop purposes
iOldTileX = pBullet->iCurrTileX;
iOldTileY = pBullet->iCurrTileY;
do
{
// check a particular location within the tile
// check for collision with the ground
iCurrAboveLevelZ = FIXEDPT_TO_INT32( pBullet->qCurrZ - qLandHeight );
//hayden not hit ground
if(ENABLE_COLLISION)
{
if (iCurrAboveLevelZ < 0)
{
// ground is in the way!
StopBullet( pBullet->iBullet );
BulletHitStructure( pBullet, INVALID_STRUCTURE_ID, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE );
return;
}
}
// check for the existence of structures
if (iNumLocalStructures == 0 && !pRoofStructure)
{ // no structures in this tile, AND THAT INCLUDES ROOFS! :-)
// new system; figure out how many steps until we cross the next edge
// and then fast forward that many steps.
iOldTileX = pBullet->iCurrTileX;
iOldTileY = pBullet->iCurrTileY;
iOldCubesZ = pBullet->iCurrCubesZ;
if (pBullet->qIncrX > 0)
{
qDistToTravelX = INT32_TO_FIXEDPT( CELL_X_SIZE ) - (pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE ));
iStepsToTravelX = qDistToTravelX / pBullet->qIncrX;
}
else if (pBullet->qIncrX < 0)
{
qDistToTravelX = pBullet->qCurrX % INT32_TO_FIXEDPT( CELL_X_SIZE );
iStepsToTravelX = qDistToTravelX / (-pBullet->qIncrX);
}
else
{
// make sure we don't consider X a limit :-)
iStepsToTravelX = 1000000;
}
if (pBullet->qIncrY > 0)
{
qDistToTravelY = INT32_TO_FIXEDPT( CELL_Y_SIZE ) - (pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE ));
iStepsToTravelY = qDistToTravelY / pBullet->qIncrY;
}
else if (pBullet->qIncrY < 0)
{
qDistToTravelY = pBullet->qCurrY % INT32_TO_FIXEDPT( CELL_Y_SIZE );
iStepsToTravelY = qDistToTravelY / (-pBullet->qIncrY);
}
else
{
// make sure we don't consider Y a limit :-)
iStepsToTravelY = 1000000;
}
// add 1 to the # of steps to travel to go INTO the next tile
iStepsToTravel = __min( iStepsToTravelX, iStepsToTravelY ) + 1;
// special coding (compared with other versions above) to deal with
// bullets hitting the ground
//hayden, stop hit ground
if(ENABLE_COLLISION)
{
if (pBullet->qCurrZ + pBullet->qIncrZ * iStepsToTravel < qLandHeight)
{
iStepsToTravel = __min( iStepsToTravel, abs( (pBullet->qCurrZ - qLandHeight) / pBullet->qIncrZ ) );
pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel;
pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel;
pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel;
StopBullet( pBullet->iBullet );
BulletHitStructure( pBullet, INVALID_STRUCTURE_ID, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE );
return;
}
}
// HEADROCK HAM B2.5: Changed condition to read fTracer flag directly from bullet's struct.
// This is for the New Tracer System.
if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER*/ ) )
|| ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE)))
{
INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS;
if ( pBullet->usFlags & ( BULLET_FLAG_SMALL_MISSILE ) )
{
bStepsPerMove = STEPS_FOR_BULLET_MOVE_SMALL_TRAILS;
}
else if ( pBullet->usFlags & ( BULLET_FLAG_FLAME ) )
{
bStepsPerMove = STEPS_FOR_BULLET_MOVE_FIRE_TRAILS;
}
for ( i = 0; i < iStepsToTravel; i++ )
{
if ( ( ( pBullet->iLoop + i ) % bStepsPerMove ) == 0 )
{
fGoingOver = TRUE;
break;
}
}
if ( fGoingOver )
{
FIXEDPT qCurrX, qCurrY, qCurrZ;
qCurrX = pBullet->qCurrX + pBullet->qIncrX * i;
qCurrY = pBullet->qCurrY + pBullet->qIncrY * i;
qCurrZ = pBullet->qCurrZ + pBullet->qIncrZ * i;
AddMissileTrail( pBullet, qCurrX, qCurrY, qCurrZ );
}
}
pBullet->qCurrX += pBullet->qIncrX * iStepsToTravel;
pBullet->qCurrY += pBullet->qIncrY * iStepsToTravel;
pBullet->qCurrZ += pBullet->qIncrZ * iStepsToTravel;
pBullet->iLoop += iStepsToTravel;
// figure out the new tile location
pBullet->iCurrTileX = FIXEDPT_TO_TILE_NUM( pBullet->qCurrX );
pBullet->iCurrTileY = FIXEDPT_TO_TILE_NUM( pBullet->qCurrY );
pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX );
pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY );
}
else
{
// there are structures in this tile
iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
// figure out the LOS cube level of the current point
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
{
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
{
// check objects on the ground
sDesiredLevel = STRUCTURE_ON_GROUND;
}
else
{
// check objects on roofs
sDesiredLevel = STRUCTURE_ON_ROOF;
iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
}
// check structures for collision
for ( iStructureLoop = 0; iStructureLoop < iNumLocalStructures; iStructureLoop++)
{
pStructure = gpLocalStructure[iStructureLoop];
if (pStructure && pStructure->sCubeOffset == sDesiredLevel)
{
if (((*(pStructure->pShape))[pBullet->bLOSIndexX][pBullet->bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0)
{
if (pStructure->fFlags & STRUCTURE_PERSON)
{
// hit someone!
fStopped = BulletHitMerc( pBullet, pStructure, fIntended );
if (fStopped)
{
// remove bullet function now called from within BulletHitMerc, so just quit
return;
}
else
{
// set pointer to null so that we don't consider hitting this person again
gpLocalStructure[iStructureLoop] = NULL;
}
}
else if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && pBullet->qCurrZ >= qWindowBottomHeight && pBullet->qCurrZ <= qWindowTopHeight)
{
fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
if (fResolveHit)
{
if (pBullet->usFlags & BULLET_FLAG_KNIFE)
{
// knives do get stopped by windows!
iRemainingImpact = HandleBulletStructureInteraction( pBullet, pStructure, &fHitStructure );
if ( iRemainingImpact <= 0 )
{
// check angle of knife and place on ground appropriately
OBJECTTYPE Object;
INT32 iKnifeGridNo;
CreateItem( pBullet->fromItem, (INT8) pBullet->ubItemStatus, &Object );
// by default knife at same tile as window
iKnifeGridNo = (INT16) iGridNo;
if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
{
if ( pBullet->qIncrX > 0)
{
// heading east so place knife on west, in same tile
}
else
{
// place to east of window
iKnifeGridNo += 1;
}
}
else
{
if (pBullet->qIncrY > 0)
{
// heading south so place wall to north, in same tile of window
}
else
{
iKnifeGridNo += WORLD_ROWS;
}
}
if ( sDesiredLevel == STRUCTURE_ON_GROUND )
{
AddItemToPool( (INT16) iKnifeGridNo, &Object, -1, 0, 0, 0 );
}
else
{
AddItemToPool( (INT16) iKnifeGridNo, &Object, -1, 0, 1, 0 );
}
// Make team look for items
NotifySoldiersToLookforItems( );
// bullet must end here!
StopBullet( pBullet->iBullet );
BulletHitStructure( pBullet, pStructure->usStructureID, 1, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE );
return;
}
}
else
{
if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
{
if ( pBullet->qIncrX > 0)
{
BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, TRUE );
LocateBullet( pBullet->iBullet );
// have to remove this window from future hit considerations so the deleted structure data can't be referenced!
gpLocalStructure[ iStructureLoop ] = NULL;
}
else
{
BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, FALSE );
LocateBullet( pBullet->iBullet );
gpLocalStructure[ iStructureLoop ] = NULL;
}
}
else
{
if (pBullet->qIncrY > 0)
{
BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, TRUE );
LocateBullet( pBullet->iBullet );
gpLocalStructure[ iStructureLoop ] = NULL;
}
else
{
BulletHitWindow( pBullet, (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS), pStructure->usStructureID, FALSE );
LocateBullet( pBullet->iBullet );
gpLocalStructure[ iStructureLoop ] = NULL;
}
}
// but the bullet keeps on going!!!
}
}
}
else if ( pBullet->iLoop > CLOSE_TO_FIRER || (pStructure->fFlags & ALWAYS_CONSIDER_HIT) || (pBullet->iLoop > CLOSE_TO_FIRER) || (fIntended) )
{
if (pStructure->fFlags & STRUCTURE_WALLSTUFF)
{
// possibly shooting at corner in which case we should let it pass
fResolveHit = ResolveHitOnWall( pStructure, iGridNo, pBullet->bLOSIndexX, pBullet->bLOSIndexY, pBullet->ddHorizAngle );
}
else
{
fResolveHit = TRUE;
}
if (fResolveHit)
{
iRemainingImpact = HandleBulletStructureInteraction( pBullet, pStructure, &fHitStructure );
if (fHitStructure)
{
// ATE: NOT if we are a special bullet like a LAW trail...
if (pStructure->fFlags & STRUCTURE_CORPSE && !( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT ) ) )
{
// ATE: In enemy territory here... ;)
// Now that we have hit a corpse, make the bugger twich!
CorpseHit( (INT16)pBullet->sGridNo, pStructure->usStructureID );
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Reducing attacker busy count..., CORPSE HIT") );
// Moved here to keep ABC >0 as long as possible
RemoveBullet( iBullet );
// ReduceAttackBusyCount( );
return;
}
else if ( iRemainingImpact <= 0 )
{
StopBullet( pBullet->iBullet );
BulletHitStructure( pBullet, pStructure->usStructureID, 1, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE );
return;
}
else if (fHitStructure && (gubLocalStructureNumTimesHit[iStructureLoop] == 0) )
{
// play animation to indicate structure being hit
BulletHitStructure( pBullet, pStructure->usStructureID, 1, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, FALSE );
gubLocalStructureNumTimesHit[iStructureLoop] = 1;
}
}
}
}
}
}
}
}
// got past everything; go to next LOS location within
// tile, horizontally or vertically
bOldLOSIndexX = pBullet->bLOSIndexX;
bOldLOSIndexY = pBullet->bLOSIndexY;
iOldCubesZ = pBullet->iCurrCubesZ;
do
{
pBullet->qCurrX += pBullet->qIncrX;
pBullet->qCurrY += pBullet->qIncrY;
//no hit on lan//pRoofStructure//hayden
if (is_networked && pRoofStructure)
{
qLastZ = pBullet->qCurrZ;
pBullet->qCurrZ += pBullet->qIncrZ;
if ( (qLastZ > qWallHeight && pBullet->qCurrZ <= qWallHeight) || (qLastZ < qWallHeight && pBullet->qCurrZ >= qWallHeight))
{
// generate roof-hitting event
// always stop with roofs
if ( 1 /*HandleBulletStructureInteraction( pBullet, pRoofStructure, &fHitStructure ) <= 0 */)
{
StopBullet( pBullet->iBullet );
BulletHitStructure( pBullet, 0, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ, TRUE );
return;
}
/*
else
{
// ATE: Found this: Should we be calling this because if we do, it will
// delete a bullet that was not supposed to be deleted....
//BulletHitStructure( pBullet, 0, 0, pBullet->pFirer, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ );
}
*/
}
}
else
{
pBullet->qCurrZ += pBullet->qIncrZ;
}
pBullet->bLOSIndexX = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrX );
pBullet->bLOSIndexY = FIXEDPT_TO_LOS_INDEX( pBullet->qCurrY );
pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) );
pBullet->iLoop++;
// HEADROCK HAM B2.5: Changed condition to read fTracer flag directly from bullet's struct.
// This is for the New Tracer System.
if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER */) )
|| ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE)))
{
INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS;
if (( pBullet->usFlags & ( BULLET_FLAG_SMALL_MISSILE /*| BULLET_FLAG_TRACER*/ ) ) || fTracer == TRUE)
{
bStepsPerMove = STEPS_FOR_BULLET_MOVE_SMALL_TRAILS;
}
else if ( pBullet->usFlags & ( BULLET_FLAG_FLAME ) )
{
bStepsPerMove = STEPS_FOR_BULLET_MOVE_FIRE_TRAILS;
}
if ( pBullet->iLoop % bStepsPerMove == 0 )
{
// add smoke trail
AddMissileTrail( pBullet, pBullet->qCurrX, pBullet->qCurrY, pBullet->qCurrZ );
}
}
}
while( (pBullet->bLOSIndexX == bOldLOSIndexX) && (pBullet->bLOSIndexY == bOldLOSIndexY) && (pBullet->iCurrCubesZ == iOldCubesZ));
pBullet->iCurrTileX = FIXEDPT_TO_INT32( pBullet->qCurrX ) / CELL_X_SIZE;
pBullet->iCurrTileY = FIXEDPT_TO_INT32( pBullet->qCurrY ) / CELL_Y_SIZE;
}
} while( (pBullet->iCurrTileX == iOldTileX) && (pBullet->iCurrTileY == iOldTileY));
if ( !GridNoOnVisibleWorldTile( (INT16) (pBullet->iCurrTileX + pBullet->iCurrTileY * WORLD_COLS) ) || (pBullet->iCurrCubesZ > PROFILE_Z_SIZE * 2 && FIXEDPT_TO_INT32( pBullet->qIncrZ ) > 0 ) )
{
// bullet outside of world!
RemoveBullet( pBullet->iBullet );
if(ENABLE_COLLISION)BulletMissed( pBullet, pBullet->pFirer );//hayden: only play event if local
return;
}
pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY , pBullet->iCurrTileX );
uiTileInc++;
if ( (pBullet->iLoop > pBullet->iRange * 2) )
{
// beyond max effective range, bullet starts to drop!
// since we're doing an increment based on distance, not time, the
// decrement is scaled down depending on how fast the bullet is (effective range)
pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2);
}
else if ( (pBullet->usFlags & BULLET_FLAG_FLAME) && ( pBullet->iLoop > pBullet->iRange ) )
{
pBullet->qIncrZ -= INT32_TO_FIXEDPT( 100 ) / (pBullet->iRange * 2);
}
// check to see if bullet is close to target
if ( pBullet->pFirer->ubTargetID != NOBODY && !(pBullet->pFirer->flags.uiStatusFlags & SOLDIER_ATTACK_NOTICED) && PythSpacesAway( (INT16) pBullet->sGridNo, (INT16) pBullet->sTargetGridNo ) <= 3 )
{
pBullet->pFirer->flags.uiStatusFlags |= SOLDIER_ATTACK_NOTICED;
}
} while( uiTileInc < pBullet->ubTilesPerUpdate );
// unless the distance is integral, after the loop there will be a
// fractional amount of distance remaining which is unchecked
// but we shouldn't(?) need to check it because the target is there!
}
INT32 CheckForCollision( FLOAT dX, FLOAT dY, FLOAT dZ, FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ, INT16 *pusStructureID, FLOAT *pdNormalX, FLOAT *pdNormalY, FLOAT *pdNormalZ )
{
INT32 iLandHeight;
INT32 iCurrAboveLevelZ;
INT32 iCurrCubesAboveLevelZ;
INT16 sDesiredLevel;
MAP_ELEMENT * pMapElement;
STRUCTURE * pStructure, *pTempStructure;
SOLDIERTYPE * pTarget;
FLOAT dTargetX;
FLOAT dTargetY;
FLOAT dTargetZMin;
FLOAT dTargetZMax;
BOOLEAN fIntended;
INT16 sX, sY, sZ;
FLOAT dOldZUnits, dZUnits;
INT8 bLOSIndexX, bLOSIndexY;
INT32 iCurrCubesZ;
sX = (INT16)( dX / CELL_X_SIZE );
sY = (INT16)( dY / CELL_Y_SIZE );
sZ = (INT16)dZ;
// Check if gridno is in bounds....
if ( !GridNoOnVisibleWorldTile( (INT16) (sX + sY * WORLD_COLS) ) )
{
// return( COLLISION_NONE );
}
if ( sX < 0 || sX > WORLD_COLS || sY < 0 || sY > WORLD_COLS )
{
// return( COLLISION_NONE );
}
// check a particular tile
// retrieve values from world for this particular tile
pMapElement = &(gpWorldLevelData[ sX + sY * WORLD_COLS] );
iLandHeight = CONVERT_PIXELS_TO_HEIGHTUNITS( pMapElement->sHeight );
// Calculate old height and new hieght in pixels
dOldZUnits = (dZ - dDeltaZ );
dZUnits = dZ;
//if (pBullet->fCheckForRoof)
//{
// if (pMapElement->pRoofHead != NULL)
// {
// fRoofPresent = TRUE;
// }
// else
// {
// fRoofPresent = FALSE;
// }
//}
//if (pMapElement->pMercHead != NULL && pBullet->iLoop != 1)
if (pMapElement->pMercHead != NULL )
{ // a merc! that isn't us :-)
pTarget = pMapElement->pMercHead->pSoldier;
dTargetX = pTarget->dXPos;
dTargetY = pTarget->dYPos;
dTargetZMin = 0.0f;
CalculateSoldierZPos( pTarget, HEIGHT, &dTargetZMax );
if (pTarget->pathing.bLevel > 0)
{
// on roof
dTargetZMin += WALL_HEIGHT_UNITS;
}
if ( sX + sY * WORLD_COLS == pTarget->sGridNo)
{
fIntended = TRUE;
}
else
{
fIntended = FALSE;
}
}
else
{
pTarget = NULL;
}
// record old tile location for loop purposes
// check for collision with the ground
iCurrAboveLevelZ = (INT32) dZ - iLandHeight;
if (iCurrAboveLevelZ < 0)
{
// ground is in the way!
if ( TERRAIN_IS_WATER( pMapElement->ubTerrainID) )
{
return ( COLLISION_WATER );
}
else
{
return ( COLLISION_GROUND );
}
}
// check for the existence of structures
pStructure = pMapElement->pStructureHead;
if (pStructure == NULL)
{ // no structures in this tile
// we can go as far as we like vertically (so long as we don't hit
// the ground), but want to stop when we get to the next tile or
// the end of the LOS path
// move 1 unit along the bullet path
//if (fRoofPresent)
//{
// dLastZ = pBullet->dCurrZ;
// (pBullet->dCurrZ) += pBullet->dIncrZ;
// if ( (dLastZ > WALL_HEIGHT && pBullet->dCurrZ < WALL_HEIGHT) || (dLastZ < WALL_HEIGHT && pBullet->dCurrZ > WALL_HEIGHT))
// {
// // generate roof-hitting event
// BulletHitStructure( pBullet->pFirer, pBullet->dCurrX, pBullet->dCurrY, pBullet->dCurrZ );
// RemoveBullet( pBullet->iBullet );
// return;
// }
//}
//else
//{
// (pBullet->dCurrZ) += pBullet->dIncrZ;
//}
// check for ground collision
if ( dZ < iLandHeight)
{
// ground is in the way!
if ( TERRAIN_IS_WATER( pMapElement->ubTerrainID) )
{
return ( COLLISION_WATER );
}
else
{
return ( COLLISION_GROUND );
}
}
if ( gfCaves || gfBasement )
{
if ( dOldZUnits > HEIGHT_UNITS && dZUnits < HEIGHT_UNITS )
{
return( COLLISION_ROOF );
}
if ( dOldZUnits < HEIGHT_UNITS && dZUnits > HEIGHT_UNITS )
{
return( COLLISION_INTERIOR_ROOF );
}
}
// check to see if we hit someone
//if (pTarget && Distance2D( dX - dTargetX, dY - dTargetY ) < HIT_DISTANCE )
//{
// well, we're in the right area; it's possible that
// we're firing over or under them though
// if ( dZ < dTargetZMax && dZ > dTargetZMin)
// {
// return( COLLISION_MERC );
// }
//}
}
else
{
// there are structures in this tile
iCurrCubesAboveLevelZ = CONVERT_HEIGHTUNITS_TO_INDEX( iCurrAboveLevelZ );
// figure out the LOS cube level of the current point
// CALCULAT LOS INDEX
bLOSIndexX = CONVERT_WITHINTILE_TO_INDEX( ((INT32)dX) % CELL_X_SIZE );
bLOSIndexY = CONVERT_WITHINTILE_TO_INDEX( ((INT32)dY) % CELL_Y_SIZE );
iCurrCubesZ = (INT32) CONVERT_HEIGHTUNITS_TO_INDEX( dZ );
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_ROOF_MAX)
{
if (iCurrCubesAboveLevelZ < STRUCTURE_ON_GROUND_MAX)
{
// check objects on the ground
sDesiredLevel = STRUCTURE_ON_GROUND;
}
else
{
// check objects on roofs
sDesiredLevel = STRUCTURE_ON_ROOF;
iCurrCubesAboveLevelZ -= STRUCTURE_ON_ROOF;
}
// check structures for collision
while (pStructure != NULL)
{
if (pStructure->fFlags & STRUCTURE_ROOF || gfCaves || gfBasement )
{
if ( dOldZUnits > HEIGHT_UNITS && dZUnits < HEIGHT_UNITS )
{
return( COLLISION_ROOF );
}
if ( dOldZUnits < HEIGHT_UNITS && dZUnits > HEIGHT_UNITS )
{
return( COLLISION_INTERIOR_ROOF );
}
}
if (pStructure->sCubeOffset == sDesiredLevel)
{
if (((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[iCurrCubesAboveLevelZ]) > 0)
{
*pusStructureID = pStructure->usStructureID;
if (pStructure->fFlags & STRUCTURE_WALLNWINDOW && dZ >= WINDOW_BOTTOM_HEIGHT_UNITS && dZ <= WINDOW_TOP_HEIGHT_UNITS)
{
if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
{
if (dDeltaX > 0)
{
return( COLLISION_WINDOW_SOUTHWEST );
}
else
{
return( COLLISION_WINDOW_NORTHWEST );
}
}
else
{
if ( dDeltaY > 0)
{
return( COLLISION_WINDOW_SOUTHEAST );
}
else
{
return( COLLISION_WINDOW_NORTHEAST );
}
}
}
if (pStructure->fFlags & STRUCTURE_WALLSTUFF )
{
//if ( !CalculateLOSNormal( pStructure, bLOSIndexX, bLOSIndexY, (INT8)iCurrCubesAboveLevelZ, dDeltaX, dDeltaY, dDeltaZ, pdNormalX, pdNormalY, pdNormalZ ) )
//{
// return( COLLISION_NONE );
//}
*pdNormalX = 0;
*pdNormalY = 0;
*pdNormalZ = 0;
if (pStructure->ubWallOrientation == INSIDE_TOP_RIGHT || pStructure->ubWallOrientation == OUTSIDE_TOP_RIGHT)
{
if (dDeltaX > 0)
{
*pdNormalX = -1;
return( COLLISION_WALL_SOUTHEAST );
}
else
{
*pdNormalX = 1;
return( COLLISION_WALL_NORTHEAST );
}
}
else
{
if ( dDeltaY > 0)
{
*pdNormalY = -1;
return( COLLISION_WALL_SOUTHWEST );
}
else
{
*pdNormalY = 1;
return( COLLISION_WALL_NORTHWEST );
}
}
}
else
{
// Determine if we are on top of this struct
// If we are a tree, not dense enough to stay!
if ( !( pStructure->fFlags & STRUCTURE_TREE ) && !( pStructure->fFlags & STRUCTURE_CORPSE ) )
{
if ( iCurrCubesAboveLevelZ < PROFILE_Z_SIZE-1 )
{
if ( !((*(pStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[ iCurrCubesAboveLevelZ + 1 ]) )
{
if ( ( pStructure->fFlags & STRUCTURE_ROOF ) )
{
return( COLLISION_ROOF );
}
else
{
return( COLLISION_STRUCTURE_Z );
}
}
}
else
{
// Search next level ( if we are ground )
if ( sDesiredLevel == STRUCTURE_ON_GROUND )
{
pTempStructure = pMapElement->pStructureHead;
// LOOK at ALL structs on roof
while ( pTempStructure != NULL )
{
if (pTempStructure->sCubeOffset == STRUCTURE_ON_ROOF )
{
if ( !((*(pTempStructure->pShape))[bLOSIndexX][bLOSIndexY] & AtHeight[ 0 ]) )
{
return( COLLISION_STRUCTURE_Z );
}
}
pTempStructure = pTempStructure->pNext;
}
}
else
{
// We are very high!
return( COLLISION_STRUCTURE_Z );
}
}
}
// Check armour rating.....
// ATE; not if small vegitation....
if ( pStructure->pDBStructureRef->pDBStructure->ubArmour != MATERIAL_LIGHT_VEGETATION )
{
if ( !(pStructure->fFlags & STRUCTURE_CORPSE ) )
{
return( COLLISION_STRUCTURE );
}
}
}
}
}
pStructure = pStructure->pNext;
}
}
// check to see if we hit someone
//if (pTarget && Distance2D( dX - dTargetX, dY - dTargetY ) < HIT_DISTANCE )
//{
// well, we're in the right area; it's possible that
// we're firing over or under them though
// if ( dZ < dTargetZMax && dZ > dTargetZMin)
// {
// return( COLLISION_MERC );
// }
// }
}
return( COLLISION_NONE );
}
INT16 gsLOSDirLUT[3][3] =
{
315, 0, 45,
270, 0, 90,
225, 180, 135
};
BOOLEAN CalculateLOSNormal( STRUCTURE *pStructure, INT8 bLOSX, INT8 bLOSY, INT8 bLOSZ, FLOAT dDeltaX, FLOAT dDeltaY, FLOAT dDeltaZ, FLOAT *pdNormalX, FLOAT *pdNormalY, FLOAT *pdNormalZ )
{
INT32 cntx, cnty;
INT8 bX, bY, tX, tY;
INT8 bNumNormals = 0;
BOOLEAN fParimeter;
vector_3 vZ, vTemp2, vNormal, vAveNormal, vTemp, vIncident;
vZ.x = 0;
vZ.y = 0;
vZ.z = 2;
vIncident.x = -1 * dDeltaX;
vIncident.y = dDeltaY;
vIncident.z = 0;
// Nomralize
vIncident = VGetNormal( &vIncident );
vAveNormal.x = 0;
vAveNormal.y = 0;
vAveNormal.z = 0;
//OK, center on xy and goforit!
for ( cntx = 0; cntx < 3; cntx++ )
{
bX = bLOSX + ( cntx - 1 );
// Check for boundry conditions, use same as one before boundary
if ( bX < 0 || bX > 4 )
{
continue;
}
for ( cnty = 0; cnty < 3; cnty++ )
{
bY = bLOSY + ( cnty - 1 );
if ( bY < 0 || bY > 4 )
{
continue;
}
if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0)
{
fParimeter = FALSE;
// THIS MUST BE THE POLYGONAL SURFACE, CHECK!
do
{
tX = (bX - 1);
tY = bY;
if ( tX >= 0 )
{
if ( ((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) <= 0)
{
fParimeter = TRUE;
break;
}
}
tX = (bX + 1);
tY = bY;
if ( tX <=4 )
{
if (((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) > 0)
{
}
else
{
fParimeter = TRUE;
break;
}
}
tX = bX;
tY = bY - 1;
if ( tX >= 0 )
{
if (((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) > 0)
{
}
else
{
fParimeter = TRUE;
break;
}
}
tX = bX;
tY = bY + 1;
if ( tX >=4 )
{
if (((*(pStructure->pShape))[tX][tY] & AtHeight[ bLOSZ ]) > 0)
{
}
else
{
fParimeter = TRUE;
break;
}
}
} while( FALSE );
// OK, now add angles, but not the center!
if ( cntx == 1 && cnty == 1 )
{
}
else //if ( fParimeter )
{
// OK< Calcluate normal using cross-product
// 1) Calculate Vector2
vTemp2.x = (float)(bX - bLOSX);
vTemp2.y = (float)(bY - bLOSY);
vTemp2.z = 1;
// Skip ones ||l to incident vector
//if ( vTemp2.x == vIncident.x && vTemp2.y == vIncident.y )
//{
// continue;
//}
//2) Calculate Normal from cross product
vNormal = VCrossProduct( &vTemp2, &vZ );
if ( VGetLength( &vNormal ) > 0 )
{
// Nomralize
vNormal = VGetNormal( &vNormal );
// CHECK ANGLE BRTWEEN INCIDENNCE AND NORMAL
//if ( VDotProduct( &vNormal, &vIncident ) > 0 )
{
bNumNormals++;
//Average normal!
vTemp = VAdd( &vNormal, &vAveNormal );
vAveNormal = VSetEqual( &vTemp );
vAveNormal = VDivScalar( &vAveNormal, bNumNormals );
// Nomralize
vAveNormal = VGetNormal( &vAveNormal );
}
}
}
}
}
}
*pdNormalX = 0;
*pdNormalY = 0;
*pdNormalZ = 0;
if ( bLOSZ < 4 )
{
if (((*(pStructure->pShape))[bLOSX][bLOSY] & AtHeight[ bLOSZ + 1 ]) > 0)
{
//*pdNormalZ = -1;
}
}
// Average angle
if ( VGetLength( &vAveNormal ) > 0 )
{
*pdNormalX = vAveNormal.x;
*pdNormalY = vAveNormal.y;
// OK done, now determine direction
if ( dDeltaX > 0 )
{
*pdNormalX *= -1;
}
if ( dDeltaY < 0 )
{
*pdNormalY *= -1;
}
return( TRUE );
}
else
{
return( FALSE );
}
}
#if 0
{
INT32 cntx, cnty;
INT8 bX, bY;
INT16 sAngleSum = 0;
INT8 bNumSums = 0;
INT16 sAngleAv;
BOOLEAN fOkX, fOkY;
BOOLEAN fGoodX, fGoodY;
//OK, center on xy and goforit!
for ( cntx = 0; cntx < 3; cntx++ )
{
bX = bLOSX + ( cntx - 1 );
// Check for boundry conditions, use same as one before boundary
if ( bX < 0 || bX > 4 )
{
fOkX = 0;
}
else
{
fOkX = 1;
}
for ( cnty = 0; cnty < 3; cnty++ )
{
fGoodX = fGoodY = TRUE;
bY = bLOSY + ( cnty - 1 );
if ( bY < 0 || bY > 4 )
{
fOkY = 0;
}
else
{
fOkY = 1;
}
if ( bY < 0 )
{
if ( fOkX )
{
bY = 0;
}
else
{
fGoodY = FALSE;
}
}
if ( bY > 4 )
{
if ( fOkX )
{
bY = 4;
}
else
{
fGoodY = FALSE;
}
}
if ( bX < 0 )
{
if ( fOkY )
{
bX = 0;
}
else
{
fGoodX = FALSE;
}
}
if ( bX > 4 )
{
if ( fOkX )
{
bX = 4;
}
else
{
fGoodX = FALSE;
}
}
if ( !fGoodX || !fGoodY )
{
continue;
}
if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0)
{
// OK, now add angles, but not the center!
if ( cntx == 1 && cnty == 1 )
{
}
else
{
sAngleSum += gsLOSDirLUT[ cntx ][cnty];
bNumSums++;
}
}
}
}
// Average angle
if ( bNumSums > 0)
{
sAngleAv = sAngleSum / bNumSums;
// This is our normal in XY plane!
// Get unit vector from this
*pdNormalX = cos( ( (float)sAngleAv / 180 ) * PI );
*pdNormalY = sin( ( (float)sAngleAv / 180 ) * PI );
// OK done, now determine direction
if ( dDeltaX > 0 )
{
*pdNormalX *= -1;
}
if ( dDeltaY > 0 )
{
*pdNormalY *= -1;
}
if ( bLOSZ == 3 )
{
// Prependicular
*pdNormalX = 0;
*pdNormalY = 0;
*pdNormalZ = -1;
}
else if ( bLOSZ > 0 && (((*(pStructure->pShape))[bLOSX][bLOSY] & AtHeight[ bLOSZ +1 ]) ) == 0)
{
*pdNormalX = 0;
*pdNormalY = 0;
*pdNormalZ = -1;
}
else
{
*pdNormalZ = 0;
}
return( TRUE );
}
else
{
*pdNormalX = 0;
*pdNormalY = 0;
return( FALSE );
}
}
{
INT32 cntx, cnty;
INT8 bX, bY;
INT16 sAngleSum = 0;
INT8 bNumSums = 0;
INT16 sAngleAv;
BOOLEAN fAlongX, fAlongY;
fAlongX = fAlongY = FALSE;
//1) LOOK ALONG X
bX = bLOSX - 1;
bY = bLOSY;
if ( bX >= 0 && bX <= 4 )
{
if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 )
{
fAlongX = TRUE;
}
}
bX = bLOSX + 1;
bY = bLOSY;
if ( bX >= 0 && bX <= 4 )
{
if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 )
{
fAlongX = TRUE;
}
}
//1) LOOK ALONG Y
bX = bLOSX;
bY = bLOSY - 1;
if ( bY >= 0 && bY <= 4 )
{
if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 )
{
fAlongY = TRUE;
}
}
bX = bLOSX;
bY = bLOSY + 1;
if ( bY >= 0 && bY <= 4 )
{
if (((*(pStructure->pShape))[bX][bY] & AtHeight[ bLOSZ ]) > 0 )
{
fAlongY = TRUE;
}
}
if ( fAlongX )
{
*pdNormalY = 1;
}
else
{
*pdNormalY = 0;
}
if ( fAlongY )
{
*pdNormalX = 1;
}
else
{
*pdNormalX = 0;
}
// OK done, now determine direction
if ( dDeltaX > 0 )
{
*pdNormalX *= -1;
}
if ( dDeltaY < 0 )
{
*pdNormalY *= -1;
}
if ( bLOSZ < 4 && (((*(pStructure->pShape))[bLOSX][bLOSY] & AtHeight[ bLOSZ +1 ]) ) == 0)
{
*pdNormalX = 0;
*pdNormalY = 0;
*pdNormalZ = -1;
}
else
{
*pdNormalZ = 0;
}
if ( *pdNormalX == 0 && *pdNormalY == 0 && *pdNormalZ == 0 )
{
return( FALSE );
}
else
{
return( TRUE );
}
}
#endif