Files
source/Tactical/DisplayCover.cpp
T
2026-02-21 21:00:38 +02:00

1788 lines
53 KiB
C++

#include "types.h"
#include "Isometric Utils.h"
#include "Overhead.h"
#include "DisplayCover.h"
#include "Font Control.h"
#include "_Ja25EnglishText.h"
#include "message.h"
#include "GameSettings.h"
#include "renderworld.h"
#include "Interface.h"
#include "worldman.h"
#include "opplist.h"
#include "LOS.h"
#include "Weapons.h"
#include "Game Clock.h"
#include "Animation Control.h"
#include "lighting.h"
// HEADROCK HAM B2.7: Allow calling a CTH approximation function for the CTH display ("F" key)
#include "UI Cursors.h"
#include "soldier profile type.h"
#include "Interface Cursors.h" // added by Flugente for UICursorDefines
#include "Rebel Command.h"
//******* Local Defines **************************************************
struct CoverCell
{
INT32 sGridNo;
INT8 bOverlayType;
bool onRoof;
CoverCell( ) : sGridNo( NOWHERE ), bOverlayType( INVALID_COVER ), onRoof( false ) {}
};
const UINT8 animArr[3] = {
ANIM_PRONE,
ANIM_CROUCH,
ANIM_STAND
};
//****** Local Variables *********************************************
INT16 gsMinCellX, gsMinCellY, gsMaxCellX, gsMaxCellY = -1;
std::vector<CoverCell> gCoverViewArea;
DWORD guiCoverNextUpdateTime = 0;
COVER_DRAW_MODE gubDrawMode = DRAW_MODE_OFF;
BOOLEAN gNoRedraw = FALSE;
//******* Local Function Prototypes ***********************************
CHAR16* GetTerrainName( const UINT8& ubTerrainType );
// anv: additional tile properties
CHAR16* GetDetailedTerrainName( ADDITIONAL_TILE_PROPERTIES_VALUES zGivenTileProperties );
void AddCoverObjectToWorld( INT32 sGridNo, UINT16 usGraphic, BOOLEAN fRoof, BOOLEAN fNightTime );
void RemoveCoverObjectFromWorld( INT32 sGridNo, UINT16 usGraphic, BOOLEAN fRoof );
void AddCoverObjectsToViewArea();
void RemoveCoverObjectsFromViewArea();
void CalculateCover();
void CalculateCoverFromEnemies();
void CalculateMines();
void CalculateTraitRange();
void CalculateTrackerRange();
void CalculateFortify();
void CalculateWeapondata();
void CalculateCoverFromEnemySoldier(SOLDIERTYPE* pFromSoldier, const INT32& sTargetGridNo, const BOOLEAN& fRoof, INT8& bOverlayType, SOLDIERTYPE* pToSoldier, const BOOLEAN& bFromSoldierCowering, const UINT8& tunnelVision, const INT8 ToSoldierStealth, const INT8 ToSoldierLBeSightAdjustment);
void GetGridNoForViewPort( const INT32& ubX, const INT32& ubY, INT32& sGridNo );
BOOLEAN GridNoOnScreenAndAround( const INT32& sGridNo, const UINT8& ubRadius=2 );
BOOLEAN IsTheRoofVisible( const INT32& sGridNo );
BOOLEAN HasAdjTile(const INT32 startIndex, const bool onRoof);
static TileDefines GetOverlayIndex( INT8 bOverlayType )
{
switch ( bOverlayType )
{
case NO_COVER:
case NO_SEE:
case MINES_ALL:
case MINE_BOMB:
case MINES_NET_1:
case MINES_LVL_4:
case TRACKS_BLOOD:
case FORTIFICATIONNODE_REMOVE:
return SPECIALTILE_COVER_1; // red
case MIN_COVER:
case MIN_SEE:
case MINE_BOMB_AND_WIRE:
case MINES_NET_2:
case MINES_LVL_3:
case TRAIT_2:
case TRACKS_VERYOLD:
case FORTIFICATIONNODE_ADJACENTADJUSTMENT:
return SPECIALTILE_COVER_2; // orange
case MED_COVER:
case MED_SEE:
case MINE_WIRE:
case MINES_NET_3:
case MINES_LVL_2:
case TRAIT_ALL:
case TRACKS_OLD:
case FORTIFICATIONNODE_NORMAL:
return SPECIALTILE_COVER_3; // yellow
case MAX_COVER:
case MAX_SEE:
case MINES_NET_4:
case MINES_LVL_1:
case TRAIT_1:
case TRACKS_RECENT:
return SPECIALTILE_COVER_5; // green
case MAX_MINES:
case MAX_TRAIT:
default:
return SPECIALTILE_COVER_4; // light green, can be used to denote that you just don't know.
}
}
//******* Functions **************************************************
///BEGIN key binding functions
void SwitchToMercView()
{
if (gubDrawMode == COVER_DRAW_MERC_VIEW)
return;
gubDrawMode = COVER_DRAW_MERC_VIEW;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzDisplayCoverText[DC_MSG__COVER_DRAW_MERC_VIEW]);
DisplayCover(TRUE);
}
void SwitchToEnemyView()
{
if (gubDrawMode == COVER_DRAW_ENEMY_VIEW)
return;
gubDrawMode = COVER_DRAW_ENEMY_VIEW;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzDisplayCoverText[DC_MSG__COVER_DRAW_ENEMY_VIEW]);
DisplayCover(TRUE);
}
void SwitchViewOff()
{
if ( gubDrawMode == DRAW_MODE_OFF )
return;
gubDrawMode = DRAW_MODE_OFF;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzDisplayCoverText[DC_MSG__COVER_DRAW_OFF]);
DisplayCover(TRUE);
}
void ToggleEnemyView()
{
if (gubDrawMode == COVER_DRAW_ENEMY_VIEW) {
SwitchViewOff();
} else {
SwitchToEnemyView();
}
}
void ToggleMercView()
{
if (gubDrawMode == COVER_DRAW_MERC_VIEW) {
SwitchViewOff();
} else {
SwitchToMercView();
}
}
///END key binding functions
void GetGridNoForViewPort( const INT32& ubX, const INT32& ubY, INT32& sGridNo )
{
sGridNo = MAPROWCOLTOPOS( ubY, ubX );
}
CHAR16* GetTerrainName( const UINT8& ubTerrainType )
{
switch(ubTerrainType) {
case LOW_GRASS:
case HIGH_GRASS:
return gzDisplayCoverText[DC_TTI__WOOD];
case FLAT_FLOOR:
case PAVED_ROAD:
return gzDisplayCoverText[DC_TTI__URBAN];
case DIRT_ROAD:
case TRAIN_TRACKS:
return gzDisplayCoverText[DC_TTI__DESERT];
case FLAT_GROUND:
return gzDisplayCoverText[DC_TTI__WOOD_AND_DESERT];
default:
return gzDisplayCoverText[DC_TTI__UNKNOWN];
}
}
CHAR16* GetDetailedTerrainName( ADDITIONAL_TILE_PROPERTIES_VALUES zGivenTileProperties )
{
if(zGivenTileProperties.bWoodCamoAffinity >= 50)
{
if(zGivenTileProperties.bDesertCamoAffinity >= 50)
{
return gzDisplayCoverText[DC_TTI__WOOD_AND_DESERT];
}
else
{
if(zGivenTileProperties.bUrbanCamoAffinity >= 50)
{
return gzDisplayCoverText[DC_TTI__WOOD_AND_URBAN];
}
else
{
if(zGivenTileProperties.bSnowCamoAffinity >= 50)
{
return gzDisplayCoverText[DC_TTI__WOOD_AND_SNOW];
}
else
{
return gzDisplayCoverText[DC_TTI__WOOD];
}
}
}
}
else if(zGivenTileProperties.bDesertCamoAffinity >= 50)
{
if(zGivenTileProperties.bUrbanCamoAffinity >= 50)
{
return gzDisplayCoverText[DC_TTI__DESERT_AND_URBAN];
}
else
{
if(zGivenTileProperties.bSnowCamoAffinity >= 50)
{
return gzDisplayCoverText[DC_TTI__DESERT_AND_SNOW];
}
else
{
return gzDisplayCoverText[DC_TTI__DESERT];
}
}
}
else if(zGivenTileProperties.bUrbanCamoAffinity >= 50)
{
if(zGivenTileProperties.bSnowCamoAffinity >= 50)
{
return gzDisplayCoverText[DC_TTI__URBAN_AND_SNOW];
}
else
{
return gzDisplayCoverText[DC_TTI__URBAN];
}
}
else if(zGivenTileProperties.bSnowCamoAffinity >= 50)
{
return gzDisplayCoverText[DC_TTI__SNOW];
}
else
{
return gzDisplayCoverText[DC_TTI__UNKNOWN];
}
}
void AddCoverObjectToWorld( INT32 sGridNo, UINT16 usGraphic, BOOLEAN fRoof, BOOLEAN fNightTime )
{
LEVELNODE *pNode;
if( fRoof )
{
AddOnRoofToHead( sGridNo, usGraphic );
pNode = gpWorldLevelData[ sGridNo ].pOnRoofHead;
}
else
{
AddObjectToHead( sGridNo, usGraphic );
pNode = gpWorldLevelData[ sGridNo ].pObjectHead;
}
pNode->uiFlags |= LEVELNODE_REVEAL;
if ( fNightTime )
{
pNode->ubShadeLevel=DEFAULT_SHADE_LEVEL;
pNode->ubNaturalShadeLevel=DEFAULT_SHADE_LEVEL;
}
}
void RemoveCoverObjectFromWorld( INT32 sGridNo, UINT16 usGraphic, BOOLEAN fRoof )
{
if( fRoof )
{
RemoveOnRoof( sGridNo, usGraphic );
}
else
{
RemoveObject( sGridNo, usGraphic );
}
}
static BOOLEAN HasAdjTile(const INT32 startIndex, const bool onRoof)
{
const auto rowLength = 1 + gsMaxCellX - gsMinCellX;
for ( INT32 ubTY = -1; ubTY <= 1; ++ubTY )
{
for ( INT32 ubTX = -1; ubTX <= 1; ++ubTX )
{
const auto i = startIndex + ubTY * rowLength + ubTX;
if ( i > 0 && i < gCoverViewArea.size() )
{
const auto& cell = gCoverViewArea[i];
if ( cell.bOverlayType > INVALID_COVER && cell.bOverlayType != MAX_COVER && cell.bOverlayType != NO_SEE && cell.onRoof == onRoof )
{
return TRUE;
}
}
}
}
return FALSE;
}
static void AddCoverObjectsToViewArea()
{
BOOLEAN fChanged = FALSE;
const BOOLEAN fNightTime = NightTime();
INT32 index = 0;
for ( auto& cell : gCoverViewArea )
{
const auto bOverlayType = cell.bOverlayType;
const auto sGridNo = cell.sGridNo;
const auto onRoof = cell.onRoof;
if ( bOverlayType != INVALID_COVER && ((bOverlayType != MAX_COVER && bOverlayType != NO_SEE) || HasAdjTile(index, onRoof)) )
{
AddCoverObjectToWorld(sGridNo, GetOverlayIndex(bOverlayType), onRoof, fNightTime);
fChanged = TRUE;
}
++index;
}
// Re-render the scene!
if ( fChanged )
{
SetRenderFlags(RENDER_FLAG_FULL);
}
}
static void RemoveCoverObjectsFromViewArea()
{
if ( gubDrawMode == DRAW_MODE_OFF && gNoRedraw )
return;
BOOLEAN fChanged = FALSE;
for ( auto& cell : gCoverViewArea )
{
INT32& sGridNo = cell.sGridNo;
INT8& bOverlayType = cell.bOverlayType;
bool& onRoof = cell.onRoof;
if ( sGridNo != NOWHERE && bOverlayType != INVALID_COVER )
{
TileDefines tile = GetOverlayIndex(bOverlayType);
RemoveCoverObjectFromWorld(sGridNo, tile, cell.onRoof);
bOverlayType = INVALID_COVER;
sGridNo = NOWHERE;
onRoof = false;
fChanged = TRUE;
}
}
// Re-render the scene!
if ( fChanged )
{
SetRenderFlags(RENDER_FLAG_FULL);
}
gNoRedraw = (gubDrawMode == DRAW_MODE_OFF);
}
static void updateCoverViewArea()
{
INT16 usTmp;
GetScreenXYWorldCell(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, &gsMinCellX, &usTmp);
GetScreenXYWorldCell(gsVIEWPORT_END_X, gsVIEWPORT_END_Y, &gsMaxCellX, &usTmp);
GetScreenXYWorldCell(gsVIEWPORT_END_X, gsVIEWPORT_START_Y, &usTmp, &gsMinCellY);
GetScreenXYWorldCell(gsVIEWPORT_START_X, gsVIEWPORT_END_Y, &usTmp, &gsMaxCellY);
// Sanity check because of array access
gsMinCellX = max(0, gsMinCellX);
gsMaxCellX = min(WORLD_COLS_MAX, gsMaxCellX);
gsMinCellY = max(0, gsMinCellY);
gsMaxCellY = min(WORLD_ROWS_MAX, gsMaxCellY);
auto i = 0;
for ( INT32 y = gsMinCellY; y <= gsMaxCellY; ++y )
{
for ( INT32 x = gsMinCellX; x <= gsMaxCellX; ++x )
{
if ( i < gCoverViewArea.size() )
{
auto& cell = gCoverViewArea[i];
INT32& sGridNo = cell.sGridNo;
GetGridNoForViewPort(x, y, sGridNo);
}
++i;
}
}
}
// ubRadius in times of y or x cell sizes
BOOLEAN GridNoOnScreenAndAround( const INT32& sGridNo, const UINT8& ubRadius )
{
INT16 sNewCenterWorldX, sNewCenterWorldY;
INT16 sWorldX;
INT16 sWorldY;
UINT16 usXDiff = ubRadius*CELL_X_SIZE;
UINT16 usYDiff = ubRadius*CELL_Y_SIZE;
ConvertGridNoToXY( sGridNo, &sNewCenterWorldX, &sNewCenterWorldY );
// Get screen coordinates for current position of soldier
GetWorldXYAbsoluteScreenXY( (INT16)(sNewCenterWorldX), (INT16)(sNewCenterWorldY), &sWorldX, &sWorldY);
if ( sWorldX >= ( gsTopLeftWorldX - usXDiff )
&& sWorldX <= ( gsBottomRightWorldX + usXDiff )
&& sWorldY >= ( gsTopLeftWorldY - usYDiff + gsVIEWPORT_WINDOW_START_Y )
&& sWorldY <= ( gsBottomRightWorldY + usYDiff ) )
{
return( TRUE );
}
return( FALSE );
}
void DisplayCover( BOOLEAN forceUpdate )
{
// Initialize gCoverArea size
static bool firstTime = true;
if ( firstTime )
{
INT16 usTmp;
GetScreenXYWorldCell(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, &gsMinCellX, &usTmp);
GetScreenXYWorldCell(gsVIEWPORT_END_X, gsVIEWPORT_END_Y, &gsMaxCellX, &usTmp);
GetScreenXYWorldCell(gsVIEWPORT_END_X, gsVIEWPORT_START_Y, &usTmp, &gsMinCellY);
GetScreenXYWorldCell(gsVIEWPORT_START_X, gsVIEWPORT_END_Y, &usTmp, &gsMaxCellY);
// Sanity check because of array access
gsMinCellX = max(0, gsMinCellX);
gsMaxCellX = min(WORLD_COLS_MAX, gsMaxCellX);
gsMinCellY = max(0, gsMinCellY);
gsMaxCellY = min(WORLD_ROWS_MAX, gsMaxCellY);
const auto cols = 1 + gsMaxCellX - gsMinCellX;
const auto rows = 1 + gsMaxCellY - gsMinCellY;
gCoverViewArea.resize(rows*cols);
firstTime = false;
}
if ( gGameExternalOptions.ubCoverDisplayUpdateWait == (UINT16)-1 )
return;
if ( forceUpdate || ( !gfScrollPending && !gfScrollInertia && GetTickCount() > guiCoverNextUpdateTime ) )
{
// remove old cover objects
RemoveCoverObjectsFromViewArea();
updateCoverViewArea();
switch ( gubDrawMode )
{
case COVER_DRAW_MERC_VIEW:
CalculateCover();
break;
case COVER_DRAW_ENEMY_VIEW:
CalculateCoverFromEnemies();
break;
case MINES_DRAW_DETECT_ENEMY:
case MINES_DRAW_PLAYERTEAM_NETWORKS:
case MINES_DRAW_NETWORKCOLOURING:
case MINES_DRAW_NET_A:
case MINES_DRAW_NET_B:
case MINES_DRAW_NET_C:
case MINES_DRAW_NET_D:
CalculateMines();
break;
case DRAW_MODE_TRAIT_RANGE:
CalculateTraitRange();
break;
case DRAW_MODE_TRACKER_SMELL:
CalculateTrackerRange();
break;
case DRAW_MODE_FORTIFY:
CalculateFortify( );
break;
case DRAW_MODE_WEAPONDATA:
CalculateWeapondata();
break;
}
guiCoverNextUpdateTime = GetTickCount() + gGameExternalOptions.ubCoverDisplayUpdateWait;
}
}
static void CalculateCoverFromEnemies()
{
if ( gusSelectedSoldier == NOBODY || gusSelectedSoldier->bActive == false )
return;
SOLDIERTYPE* pSoldier = gusSelectedSoldier;
const INT8 OurSoldierStealth = GetStealth(pSoldier);
const INT8 OurSoldierLBESightAdjustment = GetSightAdjustmentBasedOnLBE(pSoldier);
// reset cover values
for ( auto& cell : gCoverViewArea )
{
cell.bOverlayType = MAX_COVER;
}
//loop through all the actors in the sector and save enemies' info for cover calculation
static std::vector<SOLDIERTYPE*> pOpponents;
static std::vector<BOOLEAN> bCowering;
static std::vector<UINT8> tunnelVision;
pOpponents.reserve(TOTAL_SOLDIERS);
bCowering.reserve(TOTAL_SOLDIERS);
tunnelVision.reserve(TOTAL_SOLDIERS);
for ( UINT32 i = 0; i < guiNumMercSlots; ++i )
{
SOLDIERTYPE* pOpponent = MercSlots[i];
// if this merc is inactive, at base, on assignment, dead, unconscious
if ( !pOpponent || pOpponent->stats.bLife < OKLIFE )
{
continue; // next merc
}
// if this man is neutral / on the same side, he's not an opponent
if ( CONSIDERED_NEUTRAL(pSoldier, pOpponent) || (pSoldier->bSide == pOpponent->bSide) )
{
continue; // next merc
}
INT8* pbPersOL = pSoldier->aiData.bOppList + pOpponent->ubID;
INT8* pbPublOL = gbPublicOpplist[OUR_TEAM] + pOpponent->ubID;
// if this opponent is unknown personally and publicly
if ( *pbPersOL != SEEN_CURRENTLY && *pbPublOL != SEEN_CURRENTLY )
{
continue; // next merc
}
pOpponents.push_back(pOpponent);
bCowering.push_back(pOpponent->IsCowering());
tunnelVision.push_back(GetPercentTunnelVision(pOpponent));
}
// Calculate cover for the whole grid, one opponent at a time.
for ( UINT32 i = 0; i < pOpponents.size(); ++i )
{
SOLDIERTYPE* pOpponent = pOpponents[i];
const BOOLEAN isCowering = bCowering[i];
const UINT8 tunnelVisionPercentage = tunnelVision[i];
for ( auto& cell : gCoverViewArea )
{
INT32& sGridNo = cell.sGridNo;
INT8& bOverlayType = cell.bOverlayType;
bool& onRoof = cell.onRoof;
if ( !GridNoOnScreenAndAround(sGridNo, 2) )
continue;
onRoof = IsTheRoofVisible(sGridNo);
if ( !NewOKDestination(pSoldier, sGridNo, false, onRoof) )
continue;
//Skip cover calculation if there already is no cover.
if ( bOverlayType == NO_COVER )
{
continue;
}
else
{
CalculateCoverFromEnemySoldier(pOpponent, sGridNo, onRoof, bOverlayType, pSoldier, isCowering, tunnelVisionPercentage, OurSoldierStealth, OurSoldierLBESightAdjustment);
}
}
}
pOpponents.resize(0);
bCowering.resize(0);
tunnelVision.resize(0);
AddCoverObjectsToViewArea();
}
void CalculateCover()
{
if ( gusSelectedSoldier == NOBODY || gusSelectedSoldier->bActive == false )
return;
SOLDIERTYPE* pSoldier = gusSelectedSoldier;
for ( auto& cell : gCoverViewArea )
{
INT32& sGridNo = cell.sGridNo;
INT8& bOverlayType = cell.bOverlayType;
bool& onRoof = cell.onRoof;
if ( !GridNoOnScreenAndAround(sGridNo, 2) )
continue;
onRoof = IsTheRoofVisible(sGridNo);
if ( !NewOKDestination(pSoldier, sGridNo, false, onRoof) )
continue;
// reset cover value
bOverlayType = MAX_COVER;
if ( gTacticalStatus.fAtLeastOneGuyOnMultiSelect ) // view of selected mercs
{
// OK, loop through all guys who are 'multi-selected' and
SoldierID cnt = gTacticalStatus.Team[gbPlayerNum].bFirstID;
for ( ; cnt <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++cnt )
{
pSoldier = cnt;
if ( pSoldier->bActive && pSoldier->bInSector )
{
if ( pSoldier->flags.uiStatusFlags & SOLDIER_MULTI_SELECTED )
{
CalculateCoverFromSoldier(pSoldier, sGridNo, onRoof, bOverlayType);
// if the tile is already NO_COVER, there's no need to continue
if ( NO_COVER == bOverlayType )
break;
}
}
}
}
else // single view from your merc
{
CalculateCoverFromSoldier(pSoldier, sGridNo, onRoof, bOverlayType);
}
// we use different enums for our merc's sight to avoid confusing inverse sight
bOverlayType = MAX_SEE - bOverlayType;
}
AddCoverObjectsToViewArea();
}
void CalculateCoverFromSoldier( SOLDIERTYPE* pFromSoldier, const INT32& sTargetGridNo, const BOOLEAN& fRoof, INT8& bOverlayType, SOLDIERTYPE* pToSoldier )
{
const UINT16 usSightLimit = pFromSoldier->GetMaxDistanceVisible(sTargetGridNo, (INT8)fRoof, CALC_FROM_WANTED_DIR);
for ( int i = 0; i<sizeof(animArr); ++i )
{
const UINT8& ubStance = animArr[i];
INT32 usAdjustedSight = 0;
if (pToSoldier == NULL) {
usAdjustedSight = usSightLimit;
} else {
usAdjustedSight = usSightLimit + usSightLimit * GetSightAdjustment( pToSoldier, GetStealth(pToSoldier), GetSightAdjustmentBasedOnLBE(pToSoldier), sTargetGridNo, (INT8) fRoof, ubStance ) /100;
}
RebelCommand::ApplyVisionModifier(pFromSoldier, usAdjustedSight);
if ( SoldierToVirtualSoldierLineOfSightTest( pFromSoldier, sTargetGridNo, (INT8) fRoof, ubStance, FALSE, usAdjustedSight ) != 0 )
{
if ( bOverlayType > i ) bOverlayType = i;
break; // we go from prone to stand, if soldier can see someone crouching, he can also see someone standing
}
}
}
static void CalculateCoverFromEnemySoldier(SOLDIERTYPE* pFromSoldier, const INT32& sTargetGridNo, const BOOLEAN& fRoof, INT8& bOverlayType, SOLDIERTYPE* pToSoldier, const BOOLEAN& bFromSoldierCowering, const UINT8& tunnelVision, const INT8 ToSoldierStealth, const INT8 ToSoldierLBeSightAdjustment)
{
// Had to extract this from SOLDIERTYPE::GetMaxDistanceVisible() function due to performance improvement from minimizing recalculating CoweringShockLevel(pSoldier) & GetPercentTunnelVision(pSoldier)
const UINT16 usSightLimit = DistanceVisible(pFromSoldier, (SoldierHasLimitedVision(pFromSoldier) ? pFromSoldier->pathing.bDesiredDirection : DIRECTION_IRRELEVANT), DIRECTION_IRRELEVANT, sTargetGridNo, (INT8)fRoof, bFromSoldierCowering, tunnelVision);
for (int i = 0; i < sizeof(animArr); ++i)
{
const UINT8& ubStance = animArr[i];
INT32 usAdjustedSight = 0;
if (pToSoldier == nullptr) {
usAdjustedSight = usSightLimit;
}
else {
usAdjustedSight = usSightLimit + usSightLimit * GetSightAdjustment(pToSoldier, ToSoldierStealth, ToSoldierLBeSightAdjustment, sTargetGridNo, (INT8)fRoof, ubStance) / 100;
}
RebelCommand::ApplyVisionModifier(pFromSoldier, usAdjustedSight);
if (SoldierToVirtualSoldierLineOfSightTest(pFromSoldier, sTargetGridNo, (INT8)fRoof, ubStance, FALSE, usAdjustedSight) != 0)
{
if (bOverlayType > i) bOverlayType = i;
break; // we go from prone to stand, if soldier can see someone crouching, he can also see someone standing
}
}
}
BOOLEAN CanSoldierSeeFloor( SOLDIERTYPE* pSoldier, INT32 sGridNo, INT8 bLevel )
{
if ( !pSoldier )
return FALSE;
UINT16 usSightLimit = pSoldier->GetMaxDistanceVisible( sGridNo, bLevel, CALC_FROM_WANTED_DIR );
if ( SoldierToVirtualSoldierLineOfSightTest( pSoldier, sGridNo, bLevel, ANIM_PRONE, FALSE, usSightLimit ) != 0 )
return TRUE;
return FALSE;
}
void DisplayRangeToTarget(SOLDIERTYPE *pSoldier, INT32 sTargetGridNo)
{
UINT16 usRange = 0;
CHAR16 zOutputString[512];
UINT8 title = (UsingNewCTHSystem() == true ? DC_MSG__NCTH_GUN_RANGE_INFORMATION : DC_MSG__GUN_RANGE_INFORMATION);
if (sTargetGridNo == NOWHERE || sTargetGridNo == 0)
{
return;
}
UINT8 ubLightLevel = LightTrueLevel(sTargetGridNo, gsInterfaceLevel);
UINT8 ubBrightness = 100 - 100 * (ubLightLevel - SHADE_MAX) / (SHADE_MIN - SHADE_MAX); // percentage
UINT8 ubTerrainType = NO_TERRAIN;
// anv: additional tile properties
ADDITIONAL_TILE_PROPERTIES_VALUES zGivenTileProperties;
memset(&zGivenTileProperties, 0, sizeof(zGivenTileProperties));
if (gGameExternalOptions.fAdditionalTileProperties)
{
zGivenTileProperties = GetAllAdditonalTilePropertiesForGrid(sTargetGridNo, gsInterfaceLevel);
}
else
{
ubTerrainType = GetTerrainTypeForGrid(sTargetGridNo, gsInterfaceLevel);
}
INT8 ubCover = -GetSightAdjustment(pSoldier, GetStealth(pSoldier), GetSightAdjustmentBasedOnLBE(pSoldier), sTargetGridNo, gsInterfaceLevel);
//display a string with cover value of current selected merc and brightness
//swprintf( zOutputString, gzDisplayCoverText[DC_MSG__COVER_INFORMATION], ubCover, GetTerrainName(ubTerrainType), ubBrightness );
//Display the msg
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, zOutputString );
if (gGameExternalOptions.fAdditionalTileProperties)
{
if (!gGameExternalOptions.fCoverTooltipDetailedTileProperties)
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE,
gzDisplayCoverText[DC_MSG__COVER_INFORMATION],
ubCover, GetDetailedTerrainName(zGivenTileProperties), ubBrightness);
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE,
gzDisplayCoverText[DC_MSG__COVER_INFORMATION_WITH_DETAILED_CAMO],
ubCover, ubBrightness);
UINT8 ubApplicableProperties = 0;
swprintf(zOutputString, L"");
if (zGivenTileProperties.bWoodCamoAffinity > 0)
{
swprintf(zOutputString + wcslen(zOutputString), gzDisplayCoverText[DC_TTI__WOOD]);
swprintf(zOutputString + wcslen(zOutputString), L": %d/100", zGivenTileProperties.bWoodCamoAffinity);
ubApplicableProperties++;
}
if (zGivenTileProperties.bDesertCamoAffinity > 0)
{
if (ubApplicableProperties)
swprintf(zOutputString + wcslen(zOutputString), L", ");
swprintf(zOutputString + wcslen(zOutputString), gzDisplayCoverText[DC_TTI__DESERT]);
swprintf(zOutputString + wcslen(zOutputString), L": %d/100", zGivenTileProperties.bDesertCamoAffinity);
ubApplicableProperties++;
}
if (zGivenTileProperties.bUrbanCamoAffinity > 0)
{
if (ubApplicableProperties)
swprintf(zOutputString + wcslen(zOutputString), L", ");
swprintf(zOutputString + wcslen(zOutputString), gzDisplayCoverText[DC_TTI__URBAN]);
swprintf(zOutputString + wcslen(zOutputString), L": %d/100", zGivenTileProperties.bUrbanCamoAffinity);
ubApplicableProperties++;
}
if (zGivenTileProperties.bSnowCamoAffinity > 0)
{
if (ubApplicableProperties)
swprintf(zOutputString + wcslen(zOutputString), L", ");
swprintf(zOutputString + wcslen(zOutputString), gzDisplayCoverText[DC_TTI__SNOW]);
swprintf(zOutputString + wcslen(zOutputString), L": %d/100", zGivenTileProperties.bSnowCamoAffinity);
ubApplicableProperties++;
}
if (zGivenTileProperties.bSoundModifier != 0)
{
if (ubApplicableProperties)
swprintf(zOutputString + wcslen(zOutputString), L", ");
swprintf(zOutputString + wcslen(zOutputString), gzDisplayCoverText[DC_TTI__DETAILED_SOUND]);
if (zGivenTileProperties.bSoundModifier > 0)
swprintf(zOutputString + wcslen(zOutputString), L": +%d", zGivenTileProperties.bSoundModifier);
else
swprintf(zOutputString + wcslen(zOutputString), L": %d", zGivenTileProperties.bSoundModifier);
ubApplicableProperties++;
}
if (zGivenTileProperties.bStealthDifficultyModifer != 0)
{
if (ubApplicableProperties)
swprintf(zOutputString + wcslen(zOutputString), L", ");
swprintf(zOutputString + wcslen(zOutputString), gzDisplayCoverText[DC_TTI__DETAILED_STEALTH]);
if (zGivenTileProperties.bStealthDifficultyModifer > 0)
swprintf(zOutputString + wcslen(zOutputString), L": +%d/100", zGivenTileProperties.bStealthDifficultyModifer);
else
swprintf(zOutputString + wcslen(zOutputString), L": %d/100", zGivenTileProperties.bStealthDifficultyModifer);
ubApplicableProperties++;
}
if (zGivenTileProperties.bTrapBonus != 0)
{
if (ubApplicableProperties)
swprintf(zOutputString + wcslen(zOutputString), L", ");
swprintf(zOutputString + wcslen(zOutputString), gzDisplayCoverText[DC_TTI__DETAILED_TRAP_LEVEL]);
if (zGivenTileProperties.bTrapBonus > 0)
swprintf(zOutputString + wcslen(zOutputString), L": +%d", zGivenTileProperties.bTrapBonus);
else
swprintf(zOutputString + wcslen(zOutputString), L": %d", zGivenTileProperties.bTrapBonus);
ubApplicableProperties++;
}
if (wcslen(zOutputString) > 0)
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, zOutputString);
}
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE,
gzDisplayCoverText[DC_MSG__COVER_INFORMATION],
ubCover, GetTerrainName(ubTerrainType), ubBrightness);
}
//Get the range to the target location
usRange = GetRangeInCellCoordsFromGridNoDiff(pSoldier->sGridNo, sTargetGridNo);
UINT8 ubItemCursor = GetActionModeCursor(pSoldier);
UINT32 uiHitChance = 0;
UINT16 usGunRange = 0;
INT8 bTempTargetLevel = pSoldier->bTargetLevel;
UINT16 usTempAttackingWeapon = pSoldier->usAttackingWeapon;
pSoldier->usAttackingWeapon = 0;
//AXP 30.03.2007: Fix CtH calculation for first shot after changing aim level (roof/ground)
pSoldier->bTargetLevel = (INT8)gsInterfaceLevel;
//if the soldier has a weapon in hand, display gun range and chance to hit
if (WeaponInHand(pSoldier))
{
// Flugente: we might be equipped with an underbarrel gun....
OBJECTTYPE* pObjhand = pSoldier->GetUsedWeapon(&pSoldier->inv[HANDPOS]);
UINT32 usItemClass = Item[pObjhand->usItem].usItemClass;
pSoldier->usAttackingWeapon = pObjhand->usItem;
// sevenfm: if using launcher or throwing knife, use CalcThrownChanceToHit
if (ubItemCursor == TRAJECTORYCURS || usItemClass == IC_THROWING_KNIFE)
{
uiHitChance = CalcThrownChanceToHit(pSoldier, sTargetGridNo, pSoldier->aiData.bShownAimTime, pSoldier->bAimShotLocation);
usGunRange = CalcMaxTossRange(pSoldier, pObjhand->usItem, TRUE) * CELL_X_SIZE;
swprintf(zOutputString, gzDisplayCoverText[DC_MSG__GUN_RANGE_INFORMATION], usRange / 10, usGunRange / 10, uiHitChance);
}
else if (usItemClass == IC_BLADE)
{
if (gfUIFullTargetFound && MercPtrs[gusUIFullTargetID])
uiHitChance = CalcChanceToStab(pSoldier, MercPtrs[gusUIFullTargetID], pSoldier->aiData.bShownAimTime);
else
uiHitChance = 0;
usGunRange = 15;
swprintf(zOutputString, gzDisplayCoverText[DC_MSG__GUN_RANGE_INFORMATION], usRange / 10, usGunRange / 10, uiHitChance);
}
else if (ubItemCursor == TOSSCURS)
{
uiHitChance = CalcThrownChanceToHit(pSoldier, sTargetGridNo, pSoldier->aiData.bShownAimTime, pSoldier->bAimShotLocation);
usGunRange = CalcMaxTossRange(pSoldier, pSoldier->inv[HANDPOS].usItem, TRUE) * CELL_X_SIZE;
swprintf(zOutputString, gzDisplayCoverText[DC_MSG__GUN_RANGE_INFORMATION], usRange / 10, usGunRange / 10, uiHitChance);
}
else
{
uiHitChance = CalcChanceToHitGun(pSoldier, sTargetGridNo, (INT8)(pSoldier->aiData.bShownAimTime), pSoldier->bAimShotLocation);
// HEADROCK HAM B2.7: CTH approximation?
if (gGameExternalOptions.fApproximateCTH)
{
uiHitChance = ChanceToHitApproximation(pSoldier, uiHitChance);
}
// HEADROCK HAM 3.6: Calculate Gun Range using formula.
usGunRange = GunRange(pObjhand, pSoldier); // SANDRO - added argument
swprintf(zOutputString, gzDisplayCoverText[title], usRange / 10, usGunRange / 10, uiHitChance);
}
}
else if (pSoldier->inv[HANDPOS].exists() && ubItemCursor == TOSSCURS)
{
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
uiHitChance = CalcThrownChanceToHit(pSoldier, sTargetGridNo, pSoldier->aiData.bShownAimTime, pSoldier->bAimShotLocation);
usGunRange = CalcMaxTossRange(pSoldier, pSoldier->inv[HANDPOS].usItem, TRUE) * CELL_X_SIZE;
swprintf(zOutputString, gzDisplayCoverText[DC_MSG__GUN_RANGE_INFORMATION], usRange / 10, usGunRange / 10, uiHitChance);
}
else if ((!pSoldier->inv[HANDPOS].exists() || Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_PUNCH) && gfUIFullTargetFound && MercPtrs[gusUIFullTargetID])
{
if (pSoldier->inv[HANDPOS].exists())
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
else
pSoldier->usAttackingWeapon = 0;
uiHitChance = CalcChanceToPunch(pSoldier, MercPtrs[gusUIFullTargetID], pSoldier->aiData.bShownAimTime);
usGunRange = 15;
swprintf(zOutputString, gzDisplayCoverText[DC_MSG__GUN_RANGE_INFORMATION], usRange / 10, usGunRange / 10, uiHitChance);
}
else
{
swprintf(zOutputString, gzDisplayCoverText[title], usRange / 10, 0, 0);
}
pSoldier->bTargetLevel = bTempTargetLevel;
pSoldier->usAttackingWeapon = usTempAttackingWeapon;
//Display the msg
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, zOutputString);
//increment the display gun range counter ( just seeing how many times people use it )
//gJa25SaveStruct.uiDisplayGunRangeCounter++;
}
BOOLEAN IsTheRoofVisible( const INT32& sGridNo )
{
if( FlatRoofAboveGridNo( sGridNo ) )
{
if( gpWorldLevelData[ sGridNo ].uiFlags & MAPELEMENT_REVEALED )
{
if( gTacticalStatus.uiFlags & SHOW_ALL_ROOFS )
return( TRUE );
else
return( FALSE );
}
else
{
return( TRUE );
}
}
return( FALSE );
}
// ----------------------------- Mines display after this ----------------------------------------
// added by Flugente
//******* Local Function Prototypes ***********************************
void AddMinesObjectsToViewArea();
void DetermineMineDisplayInTile( const INT32 sGridNo, const INT8 bLevel, INT8& bOverlayType, const BOOLEAN fWithMineDetector = FALSE );
//******* Functions **************************************************
///BEGIN key binding functions
void SwitchToTrapNetworkView()
{
if ( gubDrawMode == MINES_DRAW_PLAYERTEAM_NETWORKS )
return;
gubDrawMode = MINES_DRAW_PLAYERTEAM_NETWORKS;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display trap network");
DisplayCover( TRUE );
}
void SwitchToHostileTrapsView()
{
if ( gubDrawMode == MINES_DRAW_DETECT_ENEMY )
return;
gubDrawMode = MINES_DRAW_DETECT_ENEMY;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display nearby traps");
DisplayCover( TRUE );
}
void SwitchMineViewOff()
{
if ( gubDrawMode < MINES_DRAW_DETECT_ENEMY || gubDrawMode > MINES_DRAW_NET_D )
return;
gubDrawMode = DRAW_MODE_OFF;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Turning off trap display");
DisplayCover( TRUE );
}
void ToggleHostileTrapsView()
{
if ( gubDrawMode == MINES_DRAW_DETECT_ENEMY ) {
SwitchMineViewOff();
} else {
SwitchToHostileTrapsView();
}
}
void ToggleTrapNetworkView()
{
switch ( gubDrawMode )
{
case MINES_DRAW_DETECT_ENEMY:
gubDrawMode = MINES_DRAW_PLAYERTEAM_NETWORKS;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display trap network" );
break;
case MINES_DRAW_PLAYERTEAM_NETWORKS:
gubDrawMode = MINES_DRAW_NETWORKCOLOURING;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display trap network colouring" );
break;
case MINES_DRAW_NETWORKCOLOURING:
gubDrawMode = MINES_DRAW_NET_A;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display trap network A" );
break;
case MINES_DRAW_NET_A:
gubDrawMode = MINES_DRAW_NET_B;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display trap network B" );
break;
case MINES_DRAW_NET_B:
gubDrawMode = MINES_DRAW_NET_C;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display trap network C" );
break;
case MINES_DRAW_NET_C:
gubDrawMode = MINES_DRAW_NET_D;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display trap network D" );
break;
case MINES_DRAW_NET_D:
gubDrawMode = DRAW_MODE_OFF;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Turning off trap display" );
break;
default:
gubDrawMode = MINES_DRAW_DETECT_ENEMY;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Display enemy traps" );
break;
}
DisplayCover( TRUE );
}
///END key binding functions
void AddMinesObjectsToViewArea()
{
BOOLEAN fSearchAdjTile = TRUE;
// no search for adjacent tiles when looking at a specific network (we have only 4 colours, need them all :-)
if ( gubDrawMode == MINES_DRAW_NETWORKCOLOURING || gubDrawMode == MINES_DRAW_NET_A || gubDrawMode == MINES_DRAW_NET_B || gubDrawMode == MINES_DRAW_NET_C || gubDrawMode == MINES_DRAW_NET_D )
fSearchAdjTile = FALSE;
const BOOLEAN fNightTime = NightTime();
INT32 index = 0;
BOOLEAN fChanged = FALSE;
for ( auto& cell : gCoverViewArea )
{
const auto bOverlayType = cell.bOverlayType;
const auto sGridNo = cell.sGridNo;
const auto onRoof = cell.onRoof;
if ( bOverlayType != INVALID_COVER && (bOverlayType != MAX_MINES || (fSearchAdjTile && HasAdjTile(index, onRoof))) )
{
if ( !TileIsOutOfBounds(sGridNo) )
{
AddCoverObjectToWorld(sGridNo, GetOverlayIndex(bOverlayType), onRoof, fNightTime);
fChanged = TRUE;
}
}
++index;
}
// Re-render the scene!
if ( fChanged )
{
SetRenderFlags( RENDER_FLAG_FULL );
}
}
void CalculateMines()
{
SOLDIERTYPE* pSoldier;
if ( gusSelectedSoldier == NOBODY || !GetSoldier(&pSoldier, gusSelectedSoldier) || !pSoldier->bInSector )
return;
// if we want to detect hostile mines and we have an metal detector in our hands, allow seeking
BOOLEAN fWithMineDetector = FALSE;
if ( pSoldier && gubDrawMode == MINES_DRAW_DETECT_ENEMY )
{
if ( FindMetalDetectorInHand(pSoldier) != NO_SLOT )
fWithMineDetector = TRUE;
// if we are looking for mines via mine detector, but don't have one equipped, return, we won't detect anything
else
return;
}
const INT32& sSelectedSoldierGridNo = gusSelectedSoldier->sGridNo;
for ( auto& cell : gCoverViewArea )
{
INT32& sGridNo = cell.sGridNo;
INT8& bOverlayType = cell.bOverlayType;
bool& onRoof = cell.onRoof;
if ( !GridNoOnScreenAndAround(sGridNo, 2) )
continue;
onRoof = IsTheRoofVisible(sGridNo);
if ( !NewOKDestination(pSoldier, sGridNo, false, onRoof) )
continue;
// if we are looking for hostile mines, but the tile is out of our' detectors range, skip looking for mines
if ( gubDrawMode == MINES_DRAW_DETECT_ENEMY && fWithMineDetector )
{
if ( PythSpacesAway(sSelectedSoldierGridNo, sGridNo) > 4 )
continue;
}
bOverlayType = INVALID_COVER;
DetermineMineDisplayInTile(sGridNo, onRoof, bOverlayType, fWithMineDetector);
}
AddMinesObjectsToViewArea();
}
void DetermineMineDisplayInTile( const INT32 sGridNo, const INT8 bLevel, INT8& bOverlayType, const BOOLEAN fWithMineDetector )
{
// if there is a bomb at that grid and level, and it isn't disabled
for (UINT32 uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; ++uiWorldBombIndex)
{
if (gWorldBombs[uiWorldBombIndex].fExists && gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].sGridNo == sGridNo && gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].ubLevel == bLevel )
{
OBJECTTYPE* pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if (!((*pObj).fFlags & OBJECT_DISABLED_BOMB))
{
// we are looking for hostile mines and have got an detector equipped
// some bombs cannot be found via metal detector
if ( gubDrawMode == MINES_DRAW_DETECT_ENEMY && fWithMineDetector && !(HasItemFlag( pObj->usItem, NO_METAL_DETECTION ) || HasItemFlag( (*pObj)[0]->data.misc.usBombItem, NO_METAL_DETECTION )) )
{
// display all mines
bOverlayType = MINE_BOMB;
}
else
{
// look for mines from our own team
if ( (*pObj)[0]->data.misc.ubBombOwner > 1 )
{
switch ( gubDrawMode )
{
case MINES_DRAW_PLAYERTEAM_NETWORKS:
{
if (ItemIsTripwire(pObj->usItem))
{
// if we're already marked as MINE_BOMB, switch to MINE_BOMB_AND_WIRE
if ( bOverlayType == MINE_BOMB )
bOverlayType = MINE_BOMB_AND_WIRE;
else if ( bOverlayType == MINE_BOMB_AND_WIRE )
;
else
{
// check if the tripwire has a gun attached
BOOLEAN fgunfound = FALSE;
attachmentList::iterator iterend = (*pObj)[0]->attachments.end();
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
{
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_GUN )
{
fgunfound = TRUE;
break;
}
}
if ( fgunfound )
bOverlayType = MINE_BOMB_AND_WIRE;
else
bOverlayType = MINE_WIRE;
}
}
else
{
// if we're already marked as MINE_WIRE, switch to MINE_BOMB_AND_WIRE
if ( bOverlayType == MINE_WIRE )
bOverlayType = MINE_BOMB_AND_WIRE;
else
bOverlayType = MINE_BOMB;
}
}
break;
case MINES_DRAW_NETWORKCOLOURING:
{
if (ItemIsTripwire(pObj->usItem))
{
// determine if wire is of the network we're searching for
// determine this tripwire's flag
UINT32 ubWireNetworkFlag = (*pObj)[0]->data.ubWireNetworkFlag;
// only if its one of our networks
if ( (ubWireNetworkFlag & TRIPWIRE_NETWORK_OWNER_PLAYER ) != 0 )
{
// correct network?
if ( (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_1 ) != 0 )
bOverlayType = MINES_NET_1;
else if ( (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_2 ) != 0 )
bOverlayType = MINES_NET_2;
else if ( (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_3 ) != 0 )
bOverlayType = MINES_NET_3;
else if ( (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_4 ) != 0 )
bOverlayType = MINES_NET_4;
}
}
}
break;
case MINES_DRAW_NET_A:
case MINES_DRAW_NET_B:
case MINES_DRAW_NET_C:
case MINES_DRAW_NET_D:
{
if (ItemIsTripwire(pObj->usItem))
{
UINT32 specificnet = 0;
switch ( gubDrawMode )
{
case MINES_DRAW_NET_A: specificnet = TRIPWIRE_NETWORK_NET_1; break;
case MINES_DRAW_NET_B: specificnet = TRIPWIRE_NETWORK_NET_2; break;
case MINES_DRAW_NET_C: specificnet = TRIPWIRE_NETWORK_NET_3; break;
case MINES_DRAW_NET_D: specificnet = TRIPWIRE_NETWORK_NET_4; break;
}
// determine if wire is of the network we're searching for
// determine this tripwire's flag
UINT32 ubWireNetworkFlag = (*pObj)[0]->data.ubWireNetworkFlag;
// correct network?
if ( (ubWireNetworkFlag & TRIPWIRE_NETWORK_OWNER_PLAYER) != 0 && (ubWireNetworkFlag & specificnet) != 0 )
{
bOverlayType = MINES_LVL_1;
if ( (ubWireNetworkFlag & ( TRIPWIRE_NETWORK_LVL_2 ) ) != 0 )
bOverlayType = MINES_LVL_2;
else if ( (ubWireNetworkFlag & ( TRIPWIRE_NETWORK_LVL_3 ) ) != 0 )
bOverlayType = MINES_LVL_3;
else if ( (ubWireNetworkFlag & ( TRIPWIRE_NETWORK_LVL_4 ) ) != 0 )
bOverlayType = MINES_LVL_4;
}
}
}
break;
case MINES_DRAW_DETECT_ENEMY:
default:
break;
}
}
}
}
}
}
}
// ----------------------------- trait range display after this ----------------------------------------
// added by Flugente
//******* Local Function Prototypes ***********************************
void AddTraitObjectsToViewArea();
UINT32 usDisplayTrait = NO_SKILLTRAIT_NT;
INT32 sTraitgridNo = NOWHERE;
///BEGIN key binding functions
void ToggleTraitRangeView(BOOLEAN fOn)
{
if ( fOn )
{
gubDrawMode = DRAW_MODE_TRAIT_RANGE;
}
else
{
gubDrawMode = DRAW_MODE_OFF;
SetTraitToDisplay( NO_SKILLTRAIT_NT );
SetGridNoForTraitDisplay( NOWHERE );
}
DisplayCover( TRUE );
}
void SetTraitToDisplay( UINT32 aTrait )
{
usDisplayTrait = aTrait;
}
void SetGridNoForTraitDisplay( INT32 sGridNo )
{
sTraitgridNo = sGridNo;
}
void CalculateTraitRange()
{
SOLDIERTYPE* pSoldier;
if ( gusSelectedSoldier == NOBODY || !GetSoldier(&pSoldier, gusSelectedSoldier) || !pSoldier->bInSector )
return;
UINT16 range1 = 0;
UINT16 range2 = 0;
switch ( usDisplayTrait )
{
case RADIO_OPERATOR_NT:
{
// we 'mark' the map position with which we called the menu, so that the player sees where he is targetting
}
break;
case VARIOUSSKILLS:
{
range1 = gGameExternalOptions.usSpotterRange;
range2 = gGameExternalOptions.usSpotterRange * 2;
}
break;
default:
return;
}
const INT32& sSelectedSoldierGridNo = gusSelectedSoldier->sGridNo;
for ( auto& cell : gCoverViewArea )
{
INT32& sGridNo = cell.sGridNo;
INT8& bOverlayType = cell.bOverlayType;
bool& onRoof = cell.onRoof;
if ( !GridNoOnScreenAndAround(sGridNo, 2) )
continue;
onRoof = IsTheRoofVisible(sGridNo);
if ( !NewOKDestination(pSoldier, sGridNo, false, onRoof) )
continue;
if ( range1 && PythSpacesAway(sSelectedSoldierGridNo, sGridNo) == range1 )
bOverlayType = TRAIT_1;
else if ( range2 && PythSpacesAway(sSelectedSoldierGridNo, sGridNo) == range2 )
bOverlayType = TRAIT_2;
// mark the gridno we are targeting
else if ( PythSpacesAway(sTraitgridNo, sGridNo) == 1 )
bOverlayType = TRAIT_1;
else
bOverlayType = MAX_TRAIT;
}
AddTraitObjectsToViewArea();
}
void AddTraitObjectsToViewArea()
{
const BOOLEAN fNightTime = NightTime( );
BOOLEAN fChanged = FALSE;
for ( auto& cell : gCoverViewArea )
{
const auto bOverlayType = cell.bOverlayType;
const auto sGridNo = cell.sGridNo;
const auto onRoof = cell.onRoof;
if ( bOverlayType == TRAIT_1 || bOverlayType == TRAIT_2 )
{
AddCoverObjectToWorld(sGridNo, GetOverlayIndex(bOverlayType), onRoof, fNightTime);
fChanged = TRUE;
}
}
// Re-render the scene!
if ( fChanged )
{
SetRenderFlags( RENDER_FLAG_FULL );
}
}
static BOOLEAN TraitTileHasAdjTile(const INT32 startIndex, const bool onRoof)
{
const auto rowLength = 1 + gsMaxCellX - gsMinCellX;
for ( INT32 ubTY = -1; ubTY <= 1; ++ubTY )
{
for ( INT32 ubTX = -1; ubTX <= 1; ++ubTX )
{
const auto i = startIndex + ubTY * rowLength + ubTX;
if ( i > 0 && i < gCoverViewArea.size() )
{
const auto& cell = gCoverViewArea[i];
if ( (cell.bOverlayType == TRAIT_1 || cell.bOverlayType == TRAIT_2) && cell.bOverlayType != NO_SEE && cell.onRoof == onRoof )
{
return TRUE;
}
}
}
}
}
// ----------------------------- tracker display after this ----------------------------------------
// added by Flugente
// idea: have smell and blood displayed as 'tracks'
// Hunter/Ranger-Trait and backgrounds add a tracker-strength. This sets a max range up to which we can display these tracks
// TODO: separate displays for smell and blood, or have the colour red symbolise blood and the rest be smell?
// toggle this on and off in the function menu
// if turned on, this will be displayed whenever the currently selected merc can track.
//******* Local Function Prototypes ***********************************
void AddTrackerObjectsToViewArea( );
void CalculateTrackerRange()
{
SOLDIERTYPE* pSoldier;
if ( gusSelectedSoldier == NOBODY || !GetSoldier(&pSoldier, gusSelectedSoldier) || !pSoldier->bInSector )
return;
const FLOAT trackerskill = (FLOAT)(NUM_SKILL_TRAITS(pSoldier, SURVIVAL_NT) * gSkillTraitValues.usSVTrackerAbility + pSoldier->GetBackgroundValue(BG_TRACKER_ABILITY)) / 100.0f;
if ( trackerskill < 0.01f )
return;
const UINT16 range = gSkillTraitValues.usSVTrackerMaxRange * trackerskill;
const INT32& sSelectedSoldierGridNo = gusSelectedSoldier->sGridNo;
for ( auto& cell : gCoverViewArea )
{
INT32& sGridNo = cell.sGridNo;
INT8& bOverlayType = cell.bOverlayType;
bool& onRoof = cell.onRoof;
if ( !GridNoOnScreenAndAround(sGridNo, 2) )
continue;
onRoof = IsTheRoofVisible(sGridNo);
if ( !NewOKDestination(pSoldier, sGridNo, false, onRoof) )
continue;
// no tracks in water or on roads
const auto terrain = GetTerrainType(sGridNo);
if ( (!onRoof && terrain == PAVED_ROAD || terrain == FLAT_FLOOR) || TERRAIN_IS_WATER(terrain) )
continue;
if ( PythSpacesAway(sSelectedSoldierGridNo, sGridNo) <= range )
{
if ( gpWorldLevelData[sGridNo].ubBloodInfo )
bOverlayType = TRACKS_BLOOD;
// blood can be seen on a roof, but other tracks can't
else if ( !onRoof && gpWorldLevelData[sGridNo].ubSmellInfo )
{
if ( gpWorldLevelData[sGridNo].ubSmellInfo < 10 )
bOverlayType = TRACKS_VERYOLD;
else if ( gpWorldLevelData[sGridNo].ubSmellInfo < 20 )
bOverlayType = TRACKS_OLD;
else //if ( gpWorldLevelData[sGridNo].ubSmellInfo < 24 )
bOverlayType = TRACKS_RECENT;
}
// we need to be able to see the floor
// this check is relatively expensive, we don't want to do this unless we have to.
// Thus we check afterwards, and only if a display value was found we reset it
if ( bOverlayType != INVALID_COVER && !CanSoldierSeeFloor(pSoldier, sGridNo, onRoof) )
bOverlayType = INVALID_COVER;
}
}
AddTrackerObjectsToViewArea();
}
void AddTrackerObjectsToViewArea( )
{
const BOOLEAN fNightTime = NightTime();
BOOLEAN fChanged = FALSE;
for ( auto& cell : gCoverViewArea )
{
const auto bOverlayType = cell.bOverlayType;
const auto sGridNo = cell.sGridNo;
const auto onRoof = cell.onRoof;
if ( bOverlayType == TRACKS_VERYOLD || bOverlayType == TRACKS_OLD || bOverlayType == TRACKS_RECENT || bOverlayType == TRACKS_BLOOD )
{
AddCoverObjectToWorld(sGridNo, GetOverlayIndex(bOverlayType), onRoof, fNightTime);
fChanged = TRUE;
}
}
// Re-render the scene!
if ( fChanged )
{
SetRenderFlags( RENDER_FLAG_FULL );
}
}
static BOOLEAN TrackerTileHasAdjTile( const INT32 startIndex )
{
const auto rowLength = 1 + gsMaxCellX - gsMinCellX;
for ( INT32 ubTY = -1; ubTY <= 1; ++ubTY )
{
for ( INT32 ubTX = -1; ubTX <= 1; ++ubTX )
{
const auto i = startIndex + ubTY * rowLength + ubTX;
if ( i > 0 && i < gCoverViewArea.size() )
{
const auto& cell = gCoverViewArea[i];
const auto bOverlayType = cell.bOverlayType;
if ( bOverlayType == TRACKS_VERYOLD || bOverlayType == TRACKS_OLD || bOverlayType == TRACKS_RECENT || bOverlayType == TRACKS_BLOOD )
{
return TRUE;
}
}
}
}
return FALSE;
}
void CalculateFortify()
{
// simply get all fortified gridnos and colour them
const auto vec = GetAllForticationGridNo();
for ( auto it = vec.begin(); it != vec.end(); ++it )
{
const auto sGridNo = (*it).first;
const auto bOverlayType = (*it).second.first;
const auto onRoof = (*it).second.second;
for ( auto& cell : gCoverViewArea ) // Linear search for matching GridNo
{
if ( cell.sGridNo == sGridNo )
{
cell.bOverlayType = bOverlayType;
cell.onRoof = onRoof;
}
}
}
BOOLEAN fChanged = FALSE;
const BOOLEAN fNightTime = NightTime();
for ( auto& cell : gCoverViewArea )
{
const auto bOverlayType = cell.bOverlayType;
const auto sGridNo = cell.sGridNo;
const auto onRoof = cell.onRoof;
if ( bOverlayType != INVALID_COVER )
{
AddCoverObjectToWorld(sGridNo, GetOverlayIndex(bOverlayType), onRoof, fNightTime);
fChanged = TRUE;
}
}
// Re-render the scene!
if ( fChanged )
{
SetRenderFlags(RENDER_FLAG_FULL);
}
}
extern INT32 gsPhysicsImpactPointGridNo;
extern UINT32 guiNewUICursor;
void CalculateWeapondata()
{
SOLDIERTYPE* pSoldier;
if ( gusSelectedSoldier == NOBODY || !GetSoldier(&pSoldier, gusSelectedSoldier) || !pSoldier->bInSector )
return;
const BOOLEAN guninhand = WeaponInHand(pSoldier);
const INT32 sSelectedSoldierGridNo = gusSelectedSoldier->sGridNo;
if ( TileIsOutOfBounds(sSelectedSoldierGridNo) )
return;
//Get the gridno the cursor is at
INT32 sMouseGridNo = NOWHERE;
GetMouseMapPos(&sMouseGridNo);
// depending on whether we have a grenade fire or a bomb to plant, different gridnos are relevant for explosives
INT32 explosivetargetgridno = NOWHERE;
// grenade cursor is stored in gsPhysicsImpactPointGridNo, we have to check whether that's valid, and whether we use that cursor right now
BOOLEAN tosscursorisvalid = FALSE;
if ( !TileIsOutOfBounds(gsPhysicsImpactPointGridNo) && (guiNewUICursor == GOOD_THROW_UICURSOR || guiNewUICursor == BAD_THROW_UICURSOR) )
{
tosscursorisvalid = TRUE;
explosivetargetgridno = gsPhysicsImpactPointGridNo;
}
else if ( !TileIsOutOfBounds(sMouseGridNo) && (guiNewUICursor == PLACE_BOMB_GREY_UICURSOR || guiNewUICursor == PLACE_BOMB_RED_UICURSOR) )
{
tosscursorisvalid = TRUE;
explosivetargetgridno = sMouseGridNo;
}
UINT16 gunrange = 0;
INT16 laserrange = 0;
UINT16 explosionradius = 0;
UINT16 fragrange = 0;
OBJECTTYPE* pObjPlatform = NULL;
OBJECTTYPE* pObjUsed = NULL;
if ( &pSoldier->inv[HANDPOS] && Item[(pSoldier->inv[HANDPOS]).usItem].usItemClass & IC_WEAPON )
{
pObjPlatform = pSoldier->GetUsedWeapon(&pSoldier->inv[HANDPOS]);
if ( pSoldier->bWeaponMode == WM_ATTACHED_GL || pSoldier->bWeaponMode == WM_ATTACHED_GL_BURST || pSoldier->bWeaponMode == WM_ATTACHED_GL_AUTO )
{
pObjUsed = FindAttachment_GrenadeLauncher(&pSoldier->inv[HANDPOS]);
}
else
{
pObjUsed = pObjPlatform;
}
gunrange = GunRange(pObjUsed, pSoldier) / CELL_X_SIZE;
laserrange = GetBestLaserRange(pObjPlatform) / CELL_X_SIZE;
if ( Item[pObjUsed->usItem].usItemClass & IC_LAUNCHER )
{
OBJECTTYPE* pAttachment = FindLaunchableAttachment(pObjPlatform, pObjUsed->usItem);
if ( pAttachment )
{
explosionradius = Explosive[Item[pAttachment->usItem].ubClassIndex].ubRadius;
fragrange = Explosive[Item[pAttachment->usItem].ubClassIndex].ubFragRange / 10;
}
}
}
else if ( &pSoldier->inv[HANDPOS] && Item[(pSoldier->inv[HANDPOS]).usItem].usItemClass & IC_EXPLOSV )
{
pObjPlatform = &pSoldier->inv[HANDPOS];
pObjUsed = pObjPlatform;
explosionradius = Explosive[Item[pObjUsed->usItem].ubClassIndex].ubRadius;
fragrange = Explosive[Item[pObjUsed->usItem].ubClassIndex].ubFragRange / 10;
}
// we have to store and later reset the soldier's target level
INT8 bTempTargetLevel = pSoldier->bTargetLevel;
for ( auto& cell : gCoverViewArea )
{
INT32& sGridNo = cell.sGridNo;
INT8& bOverlayType = cell.bOverlayType;
bool& onRoof = cell.onRoof;
if ( !GridNoOnScreenAndAround(sGridNo, 2) )
continue;
onRoof = IsTheRoofVisible(sGridNo);
if ( !NewOKDestination(pSoldier, sGridNo, false, onRoof) )
continue;
if ( tosscursorisvalid && explosionradius > 0 && PythSpacesAway(explosivetargetgridno, sGridNo) <= explosionradius )
bOverlayType = NO_COVER;
else if ( tosscursorisvalid && fragrange > 0 && PythSpacesAway(explosivetargetgridno, sGridNo) <= fragrange )
bOverlayType = MIN_COVER;
else
{
if ( GetDirectionToGridNoFromGridNo(sSelectedSoldierGridNo, sGridNo) != pSoldier->ubDirection )
continue;
if ( guninhand && gunrange > 0 && PythSpacesAway(sSelectedSoldierGridNo, sGridNo) <= gunrange )
{
pSoldier->bTargetLevel = onRoof;
UINT32 uiHitChance = CalcChanceToHitGun(pSoldier, sGridNo, (INT8)(pSoldier->aiData.bShownAimTime), pSoldier->bAimShotLocation);
if ( uiHitChance > 75 )
bOverlayType = MAX_COVER;
else if ( uiHitChance > 50 )
bOverlayType = MED_COVER;
else if ( uiHitChance > 25 )
bOverlayType = MIN_COVER;
else if ( uiHitChance > 0 )
bOverlayType = NO_COVER;
}
}
}
// important: reset target level to what it really was
pSoldier->bTargetLevel = bTempTargetLevel;
BOOLEAN fChanged = FALSE;
const BOOLEAN fNightTime = NightTime();
for ( auto& cell : gCoverViewArea )
{
if ( cell.bOverlayType != INVALID_COVER )
{
AddCoverObjectToWorld(cell.sGridNo, GetOverlayIndex(cell.bOverlayType), cell.onRoof, fNightTime);
fChanged = TRUE;
}
}
// Re-render the scene!
if ( fChanged )
{
SetRenderFlags(RENDER_FLAG_FULL);
}
}