Files
source/TileEngine/Explosion Control.cpp
Marco Antonio Jaguaribe CostaandAsdow 8b1c4effa0 nuke all the #if 0's
scripted commit:

find . -not -path '*/.*' -and -not -path '*/ext*' -type d -exec coan source -R -E {} ';'

https://coan2.sourceforge.net/coan_man_1.html
2023-01-19 00:13:08 +02:00

6489 lines
209 KiB
C++

#include "builddefines.h"
#include "debug.h"
#include "weapons.h"
#include "worldman.h"
#include "game clock.h"
#include "renderworld.h"
#include "explosion control.h"
#include "sound control.h"
#include "structure wrap.h"
#include "interactive tiles.h"
#include "SaveLoadMap.h"
#include "Message.h"
#include "Random.h"
#include "smokeeffects.h"
#include "pathai.h"
#include "strategic.h"
#include "Action Items.h"
#include "Interface Dialogue.h"
#include "LightEffects.h"
#include "AI.h"
#include "Soldier tile.h"
#include "Render Fun.h"
#include "Opplist.h"
#include "smell.h"
#include "end game.h"
#include "Buildings.h"
#include "fov.h"
#include "Map Information.h"
#include "Soldier Functions.h"//dnl ch40 200909
#include "Text.h" // added by SANDRO
#include "campaign.h" // yet another one added
#include "CampaignStats.h" // added by Flugente
#include "Points.h" // added by Flugente
#include "Interface Control.h" // added by Flugente for DrawExplosionWarning(...)
#include "SkillMenu.h"
#include "Soldier Macros.h"
#include "connect.h"
#include "debug control.h"
#include "LuaInitNPCs.h"
#include "Luaglobal.h"
#include "Item Statistics.h"
#ifdef JA2UB
#include "interface Dialogue.h"
#include "Ja25_Tactical.h"
#include "Ja25 Strategic Ai.h"
#include "Dialogue Control.h"
#include "ub_config.h"
#endif
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
class SOLDIERTYPE;
//DBrot: More Rooms
BOOLEAN HookerInRoom( UINT16 usRoom );
// MODULE FOR EXPLOSIONS
// Spreads the effects of explosions...
BOOLEAN ExpAffect( INT32 sBombGridNo, INT32 sGridNo, UINT32 uiDist, UINT16 usItem, UINT8 ubOwner, INT16 sSubsequent, BOOLEAN *pfMercHit, INT8 bLevel, INT32 iSmokeEffectID );
// Flashbang effect on soldier
UINT8 DetermineFlashbangEffect( SOLDIERTYPE *pSoldier, INT8 ubExplosionDir, BOOLEAN fInBuilding);
// HEADROCK HAM 5.1: Explosion Fragments launcher
void FireFragments( UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, UINT16 usItem, UINT8 ubDirection = DIRECTION_IRRELEVANT );
// Flugente: shoot a gun without anyone operating it (used for makeshift traps wih guns)
void FireFragmentsTrapGun( SOLDIERTYPE* pThrower, INT32 gridno, INT16 sZ, OBJECTTYPE* pObj, UINT8 ubDirection = NORTH );
extern INT8 gbSAMGraphicList[ MAX_NUMBER_OF_SAMS ];
extern void AddToShouldBecomeHostileOrSayQuoteList( UINT8 ubID );
extern void RecompileLocalMovementCostsForWall( INT32 sGridNo, UINT8 ubOrientation );
void FatigueCharacter( SOLDIERTYPE *pSoldier );
#ifdef JA2UB
void HandleSeeingFortifiedDoor( UINT32 sGridNo );//Ja25 UB
#endif
#define NO_ALT_SOUND -1
EXPLOSION_DATA gExpAniData[ NUM_EXP_TYPES ] =
{
// Trans Damage Explosion Alternative Explosion Animation
// Key Key Sound Explosion Animation Speed
// Frame Frame ID SoundID Filename
{0, 0, EXPLOSION_1, EXPLOSION_ALT_BLAST_1, "", 0},
{17, 3, EXPLOSION_1, EXPLOSION_ALT_BLAST_1, "TILECACHE\\ZGRAV_D.STI", 80},
{28, 5, EXPLOSION_BLAST_2, NO_ALT_SOUND, "TILECACHE\\ZGRAV_C.STI", 80},
{24, 5, EXPLOSION_BLAST_2, NO_ALT_SOUND, "TILECACHE\\ZGRAV_B.STI", 80},
{1, 5, EXPLOSION_1, EXPLOSION_ALT_BLAST_1, "TILECACHE\\shckwave.STI", 20},
{1, 18, AIR_ESCAPING_1, NO_ALT_SOUND, "TILECACHE\\WAT_EXP.STI", 80},
{1, 18, AIR_ESCAPING_1, NO_ALT_SOUND, "TILECACHE\\TEAR_EXP.STI", 80},
{1, 18, AIR_ESCAPING_1, NO_ALT_SOUND, "TILECACHE\\TEAR_EXP.STI", 80},
{1, 18, AIR_ESCAPING_1, NO_ALT_SOUND, "TILECACHE\\MUST_EXP.STI", 80}
};
//UINT8 ubTransKeyFrame[ NUM_EXP_TYPES ] =
//{
// 0,
// 17,
// 28,
// 24,
// 1,
// 1,
// 1,
// 1,
// 1,
//};
//
//UINT8 ubDamageKeyFrame[ NUM_EXP_TYPES ] =
//{
// 0,
// 3,
// 5,
// 5,
// 5,
// 18,
// 18,
// 18,
// 18,
//};
//
//
//UINT32 uiExplosionSoundID[ NUM_EXP_TYPES ] =
//{
// EXPLOSION_1,
// EXPLOSION_1,
// EXPLOSION_BLAST_2, //LARGE
// EXPLOSION_BLAST_2,
// EXPLOSION_1,
// AIR_ESCAPING_1,
// AIR_ESCAPING_1,
// AIR_ESCAPING_1,
// AIR_ESCAPING_1,
//};
//
//
//CHAR8 zBlastFilenames[][70] =
//{
// "",
// "TILECACHE\\ZGRAV_D.STI",
// "TILECACHE\\ZGRAV_C.STI",
// "TILECACHE\\ZGRAV_B.STI",
// "TILECACHE\\shckwave.STI",
// "TILECACHE\\WAT_EXP.STI",
// "TILECACHE\\TEAR_EXP.STI",
// "TILECACHE\\TEAR_EXP.STI",
// "TILECACHE\\MUST_EXP.STI",
//};
//
//CHAR8 sBlastSpeeds[] =
//{
// 0,
// 80,
// 80,
// 80,
// 20,
// 80,
// 80,
// 80,
// 80,
//};
#define BOMB_QUEUE_DELAY (1000 + Random( 500 ) )
#define MAX_BOMB_QUEUE 40
ExplosionQueueElement gExplosionQueue[MAX_BOMB_QUEUE];
UINT8 gubElementsOnExplosionQueue = 0;
BOOLEAN gfExplosionQueueActive = FALSE;
BOOLEAN gfExplosionQueueMayHaveChangedSight = FALSE;
UINT8 gubPersonToSetOffExplosions = NOBODY;
INT32 gsTempActionGridNo = NOWHERE;
extern UINT8 gubInterruptProvoker;
#define NUM_EXPLOSION_SLOTS 100
// GLOBAL FOR SMOKE LISTING
EXPLOSIONTYPE gExplosionData[ NUM_EXPLOSION_SLOTS ];
UINT32 guiNumExplosions = 0;
INT32 GetFreeExplosion( void );
void RecountExplosions( void );
void GenerateExplosionFromExplosionPointer( EXPLOSIONTYPE *pExplosion );
void HandleBuldingDestruction( INT32 sGridNo, UINT8 ubOwner );
#ifdef JA2UB
//JA25 UB
void HavePersonAtGridnoStop( UINT32 sGridNo );
BOOLEAN ShouldThePlayerStopWhenWalkingOnBiggensActionItem( UINT8 ubRecordNum );
void HandleDestructionOfPowerGenFan();
BOOLEAN IsFanGraphicInSectorAtThisGridNo( UINT32 sGridNo );
void HandleExplosionsInTunnelSector( UINT32 sGridNo );
void HandleSwitchToOpenFortifiedDoor( UINT32 sGridNo );
void HandleSeeingPowerGenFan( UINT32 sGridNo );
#endif
// Flugente: for roof destruction
extern UINT8 gubMaterialArmour[];
// defines how much of a roofs tiles armour partially counts for neighbouring tiles - this is used in calculating the 'structural integrity' of a roof
#define WALLSTRENGTH_NEIGHBOURING_PER_TILE 0.75f
INT32 GetFreeExplosion( void )
{
UINT32 uiCount;
for(uiCount=0; uiCount < guiNumExplosions; uiCount++)
{
if(( gExplosionData[uiCount].fAllocated==FALSE ) )
return( (INT32)uiCount );
}
if( guiNumExplosions < NUM_EXPLOSION_SLOTS )
return( (INT32) guiNumExplosions++ );
return( -1 );
}
void RecountExplosions( void )
{
INT32 uiCount;
for(uiCount=guiNumExplosions-1; (uiCount >=0) ; uiCount--)
{
if( ( gExplosionData[uiCount].fAllocated ) )
{
guiNumExplosions=(UINT32)(uiCount+1);
break;
}
}
}
extern void HandleLoyaltyForDemolitionOfBuilding( SOLDIERTYPE *pSoldier, INT16 sPointsDmg );
// GENERATE EXPLOSION
void InternalIgniteExplosion( UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, INT32 sGridNo, UINT16 usItem, BOOLEAN fLocate, INT8 bLevel, UINT8 ubDirection, OBJECTTYPE * pObj )
{
#ifdef JA2BETAVERSION
if (is_networked) {
CHAR tmpMPDbgString[512];
sprintf(tmpMPDbgString,"InternalIgniteExplosion ( ubOwner : %i , sX : %i , sY : %i , sZ : %i , sGridNo : %i , usItem : %i , fLocate : %i , bLevel : %i , ubDirection : %i )\n",ubOwner, sX , sY , sZ , sGridNo , usItem , (int)fLocate , bLevel, ubDirection );
MPDebugMsg(tmpMPDbgString);
}
#endif
EXPLOSION_PARAMS ExpParams ;
// Callahan start
// Double check that we are using an explosive!
// Check if there is an explosive or an attacker
if ( !( Item[ usItem ].usItemClass & IC_EXPLOSV ) && ubOwner == NOBODY )
{
return; // no explosive / no attacker
}
// The ammotype used. WARNING! This will only be != 0 if ubOwner != NOBODY !
UINT16 ammotype = 0;
// Okay, we either got an explosive or a real attacker to check for.
// Let's check for the attacker first.
if ( ubOwner != NOBODY )
{
OBJECTTYPE* pUsedGun = MercPtrs[ ubOwner ]->GetUsedWeapon( &MercPtrs [ ubOwner ]->inv[MercPtrs[ubOwner]->ubAttackingHand] );
ammotype = (*pUsedGun)[0]->data.gun.ubGunAmmoType;
if ( !( Item[ usItem ].usItemClass & IC_EXPLOSV ) && AmmoTypes[ammotype].explosionSize < 2 )
{
return; // no explosive and attackers gun is not fireing HE
}
// Flugente: campaign stats
if ( MercPtrs[ ubOwner ]->bTeam == OUR_TEAM )
{
if ( Item[ usItem ].usItemClass & IC_EXPLOSV )
gCampaignStats.AddConsumption(CAMPAIGN_CONSUMED_EXPLOSIVES, (FLOAT)(Item[usItem].ubWeight) );
}
}
// Increment attack counter...
if (gubElementsOnExplosionQueue == 0)
{
// single explosion, disable sight until the end, and set flag
// to check sight at end of attack
gTacticalStatus.uiFlags |= (DISALLOW_SIGHT | CHECK_SIGHT_AT_END_OF_ATTACK);
}
// gTacticalStatus.ubAttackBusyCount++;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Incrementing Attack: Explosion gone off, COunt now %d", gTacticalStatus.ubAttackBusyCount ) );
// OK, go on!
ExpParams.uiFlags = EXPLOSION_FLAG_USEABSPOS;
ExpParams.ubOwner = ubOwner;
// No explosive but an attacker with HE ammo.
if ( !( Item[ usItem ].usItemClass & IC_EXPLOSV ) && ubOwner != NOBODY)
{
ExpParams.ubTypeID = (INT8)Explosive[Item[AmmoTypes[ammotype].highExplosive].ubClassIndex].ubAnimationID;
// return;
}
else // just normal explosives should get here
{
ExpParams.ubTypeID = (INT8)Explosive[ Item[ usItem ].ubClassIndex ].ubAnimationID;
}
// Callahan end
// Flugente: bouncing grenades/jumping mines explode from about waist heigth, so we add 25 to sZ. However, on order for this to work on roofs, we have to add another 50
// if on a roof, as heigth is only added if bLevel = 1 and sZ = 0
if ( HasItemFlag(usItem, JUMP_GRENADE) )
{
sZ += 25;
if ( bLevel )
sZ += ROOF_LEVEL_HEIGHT;
}
ExpParams.sX = sX;
ExpParams.sY = sY;
ExpParams.sZ = sZ;
ExpParams.sGridNo = sGridNo;
ExpParams.usItem = usItem;
ExpParams.fLocate = fLocate;
ExpParams.bLevel = bLevel;
GenerateExplosion( &ExpParams );
// Flugente: if the explosion occured in a building, this might lower loyalty in town
UINT16 tmp;
if ( Item[ usItem ].usItemClass & IC_EXPLOSV && ubOwner != NOBODY && ubOwner < NUM_PROFILES && InARoom( sGridNo, &tmp ) )
{
HandleLoyaltyForDemolitionOfBuilding( MercPtrs[ubOwner], Explosive[ Item[ usItem ].ubClassIndex ].ubDamage );
// Flugente: campaign stats
gCurrentIncident.usIncidentFlags |= INCIDENT_BUILDINGS_DAMAGED;
}
// HEADROCK HAM 5.1: Launch fragments from the explosion.
// sevenfm: no fragments in water
if (Explosive[Item[usItem].ubClassIndex].usNumFragments > 0 && !Water(sGridNo, bLevel))
{
// HEADROCK HAM 5: Deactivated until the release of HAM 5.1.
FireFragments( ubOwner, sX, sY, sZ, usItem, ubDirection );
}
// Flugente: Items can have secondary explosions
HandleBuddyExplosions(ubOwner, sX, sY, sZ, sGridNo, usItem, fLocate, bLevel, ubDirection );
// sevenfm: handle explosive items from attachments
if( gGameExternalOptions.bAllowExplosiveAttachments )
HandleAttachedExplosions(ubOwner, sX, sY, sZ, sGridNo, usItem, fLocate, bLevel, ubDirection, pObj );
// sevenfm: add smoke effect, only for normal explosions
if (gGameExternalOptions.bAddSmokeAfterExplosion &&
(bLevel == 0 || IsRoofPresentAtGridNo(sGridNo)) &&
!Water(sGridNo, bLevel) &&
GridNoOnVisibleWorldTile(sGridNo) &&
Explosive[Item[usItem].ubClassIndex].ubType == 0 &&
Item[usItem].usBuddyItem == 0 &&
Explosive[Item[usItem].ubClassIndex].ubDamage > 20)
{
UINT16 usSmokeItem = GetHandGrenadeOfType(SMOKE_GRENADE, EXPLOSV_SMOKE);
if (usSmokeItem)
{
if (HasItemFlag(usItem, JUMP_GRENADE))
NewSmokeEffect(sGridNo, usSmokeItem, bLevel, ubOwner, 0, 1, 0);
else
NewSmokeEffect(sGridNo, usSmokeItem, bLevel, ubOwner, 0, 2, 1);
}
}
}
void IgniteExplosion( UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, INT32 sGridNo, UINT16 usItem, INT8 bLevel, UINT8 ubDirection, OBJECTTYPE * pObj )
{
InternalIgniteExplosion( ubOwner, sX, sY, sZ, sGridNo, usItem, TRUE, bLevel, ubDirection, pObj );
}
void GenerateExplosion( EXPLOSION_PARAMS *pExpParams )
{
EXPLOSIONTYPE *pExplosion;
UINT32 uiFlags;
UINT8 ubOwner;
UINT8 ubTypeID;
INT16 sX;
INT16 sY;
INT16 sZ;
INT32 sGridNo;
UINT16 usItem;
INT32 iIndex;
INT8 bLevel;
// Assign param values
uiFlags = pExpParams->uiFlags;
ubOwner = pExpParams->ubOwner;
ubTypeID = pExpParams->ubTypeID;
sX = pExpParams->sX;
sY = pExpParams->sY;
sZ = pExpParams->sZ;
sGridNo = pExpParams->sGridNo;
usItem = pExpParams->usItem;
bLevel = pExpParams->bLevel;
{
// GET AND SETUP EXPLOSION INFO IN TABLE....
iIndex = GetFreeExplosion( );
if ( iIndex == -1 )
{
return;
}
// OK, get pointer...
pExplosion = &( gExplosionData[ iIndex ] );
memset( pExplosion, 0, sizeof( EXPLOSIONTYPE ) );
// Setup some data...
memcpy( &(pExplosion->Params), pExpParams, sizeof( EXPLOSION_PARAMS ) );
pExplosion->fAllocated = TRUE;
pExplosion->iID = iIndex;
GenerateExplosionFromExplosionPointer( pExplosion );
}
// ATE: Locate to explosion....
if ( pExpParams->fLocate )
{
LocateGridNo( sGridNo );
}
}
void GenerateExplosionFromExplosionPointer( EXPLOSIONTYPE *pExplosion )
{
UINT32 uiFlags;
UINT8 ubOwner;
UINT8 ubTypeID;
INT16 sX;
INT16 sY;
INT16 sZ;
INT32 sGridNo;
UINT16 usItem;
UINT8 ubTerrainType;
INT8 bLevel;
INT32 sSoundID;
ANITILE_PARAMS AniParams;
// Assign param values
uiFlags = pExplosion->Params.uiFlags;
ubOwner = pExplosion->Params.ubOwner;
ubTypeID = pExplosion->Params.ubTypeID;
sX = pExplosion->Params.sX;
sY = pExplosion->Params.sY;
sZ = pExplosion->Params.sZ;
sGridNo = pExplosion->Params.sGridNo;
usItem = pExplosion->Params.usItem;
bLevel = pExplosion->Params.bLevel;
// If Z value given is 0 and bLevel > 0, make z heigher
if ( sZ == 0 && bLevel > 0 )
{
sZ = ROOF_LEVEL_HEIGHT;
}
pExplosion->iLightID = -1;
// OK, if we are over water.... use water explosion...
ubTerrainType = GetTerrainType( sGridNo );
// Setup explosion!
memset( &AniParams, 0, sizeof( ANITILE_PARAMS ) );
AniParams.sGridNo = sGridNo;
AniParams.ubLevelID = ANI_TOPMOST_LEVEL;
AniParams.sDelay = gExpAniData[ ubTypeID ].sBlastSpeed; // Lesh: edit this line
AniParams.sStartFrame = pExplosion->sCurrentFrame;
AniParams.uiFlags = ANITILE_CACHEDTILE | ANITILE_FORWARD | ANITILE_EXPLOSION;
// sevenfm: check level
//if ( TERRAIN_IS_WATER(ubTerrainType) )
if (Water(sGridNo, bLevel))
{
// Change type to water explosion...
ubTypeID = WATER_BLAST;
AniParams.uiFlags |= ANITILE_ALWAYS_TRANSLUCENT;
}
if ( sZ < WALL_HEIGHT )
{
AniParams.uiFlags |= ANITILE_NOZBLITTER;
}
if ( uiFlags & EXPLOSION_FLAG_USEABSPOS )
{
AniParams.sX = sX;
AniParams.sY = sY;
AniParams.sZ = sZ;
//AniParams.uiFlags |= ANITILE_USEABSOLUTEPOS;
}
AniParams.ubKeyFrame1 = gExpAniData[ ubTypeID ].ubTransKeyFrame; // Lesh: edit this line
AniParams.uiKeyFrame1Code = ANI_KEYFRAME_BEGIN_TRANSLUCENCY;
if ( !( uiFlags & EXPLOSION_FLAG_DISPLAYONLY ) )
{
AniParams.ubKeyFrame2 = gExpAniData[ ubTypeID ].ubDamageKeyFrame; // Lesh: edit this line
AniParams.uiKeyFrame2Code = ANI_KEYFRAME_BEGIN_DAMAGE;
}
AniParams.uiUserData = usItem;
AniParams.ubUserData2 = ubOwner;
AniParams.uiUserData3 = pExplosion->iID;
strcpy( AniParams.zCachedFile, gExpAniData[ ubTypeID ].zBlastFilename ); // Lesh: edit this line
// A little safety here, for just in case. If it fails to create an explosion tile, don't increase the attack busy count.
// But if it succeeds, do it here. Don't futz with the count in other locations when it can be centralized!
if (CreateAnimationTile( &AniParams ) )
{
gTacticalStatus.ubAttackBusyCount++;
DebugAttackBusy( String( "Explosion started. Incrementing attack busy, now %d\n", gTacticalStatus.ubAttackBusyCount ) );
}
// set light source for flashbangs.... or...
if ( pExplosion->Params.ubTypeID == FLASHBANG_EXP )
{
pExplosion->iLightID = LightSpriteCreate("FLSHBANG.LHT", 0 );
}
// generic light
// DO ONLY IF WE'RE AT A GOOD LEVEL
else if ( ubAmbientLightLevel >= MIN_AMB_LEVEL_FOR_MERC_LIGHTS )
{
pExplosion->iLightID = LightSpriteCreate("L-R04.LHT", 0 );
}
if( pExplosion->iLightID != -1 )
{
LightSpritePower ( pExplosion->iLightID, TRUE );
LightSpriteRoofStatus( pExplosion->iLightID, pExplosion->Params.bLevel );
LightSpritePosition ( pExplosion->iLightID, (INT16)(sX/CELL_X_SIZE), (INT16)(sY/CELL_Y_SIZE) );
}
// Lesh: sound randomization
sSoundID = gExpAniData[ubTypeID].sExplosionSoundID;
// Randomize
if ( gExpAniData[ubTypeID].sAltExplosionSoundID > NO_ALT_SOUND && (Random( 2 ) || sSoundID < 0) )
sSoundID = gExpAniData[ubTypeID].sAltExplosionSoundID;
// Lesh: sound randomization ends
if ( sSoundID > NO_ALT_SOUND )
PlayJA2Sample( sSoundID, RATE_11025, SoundVolume( HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
}
void UpdateExplosionFrame( INT32 iIndex, INT16 sCurrentFrame )
{
gExplosionData[ iIndex ].sCurrentFrame = sCurrentFrame;
// Lesh: make sparkling effect
if ( gExplosionData[iIndex].Params.ubTypeID == FLASHBANG_EXP )
{
if ( gExplosionData[iIndex].iLightID != -1 )
{
INT16 iX, iY;
iX = (INT16) (gExplosionData[iIndex].Params.sX/CELL_X_SIZE + Random(3) - 1);
iY = (INT16) (gExplosionData[iIndex].Params.sY/CELL_Y_SIZE + Random(3) - 1);
LightSpritePosition( gExplosionData[iIndex].iLightID, iX, iY);
}
}
}
void RemoveExplosionData( INT32 iIndex )
{
gExplosionData[ iIndex ].fAllocated = FALSE;
if ( gExplosionData[ iIndex ].iLightID != -1 )
{
LightSpriteDestroy( gExplosionData[ iIndex ].iLightID );
}
}
void HandleFencePartnerCheck( INT32 sStructGridNo )
{
STRUCTURE *pFenceStructure, *pFenceBaseStructure;
LEVELNODE *pFenceNode;
INT8 bFenceDestructionPartner = -1;
UINT32 uiFenceType;
UINT16 usTileIndex;
pFenceStructure = FindStructure( sStructGridNo, STRUCTURE_FENCE );
if ( pFenceStructure )
{
// How does our explosion partner look?
if ( pFenceStructure->pDBStructureRef->pDBStructure->bDestructionPartner < 0 )
{
// Find level node.....
pFenceBaseStructure = FindBaseStructure( pFenceStructure );
// Get LEVELNODE for struct and remove!
pFenceNode = FindLevelNodeBasedOnStructure( pFenceBaseStructure->sGridNo, pFenceBaseStructure );
// Get type from index...
GetTileType( pFenceNode->usIndex, &uiFenceType );
bFenceDestructionPartner = -1 * ( pFenceBaseStructure->pDBStructureRef->pDBStructure->bDestructionPartner );
// Get new index
GetTileIndexFromTypeSubIndex( uiFenceType, (INT8)( bFenceDestructionPartner ), &usTileIndex );
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
// Remove it!
RemoveStructFromLevelNode( pFenceBaseStructure->sGridNo, pFenceNode );
// Add it!
AddStructToHead( pFenceBaseStructure->sGridNo, (UINT16)( usTileIndex ) );
ApplyMapChangesToMapTempFile( FALSE );
}
}
}
INT8 ExplosiveDamageStructureAtGridNo( STRUCTURE * pCurrent, STRUCTURE **ppNextCurrent, INT32 sGridNo, INT16 sWoundAmt, UINT32 uiDist,
BOOLEAN *pfRecompileMovementCosts, BOOLEAN fOnlyWalls, BOOLEAN fSubSequentMultiTilesTransitionDamage, UINT8 ubOwner, INT8 bLevel,
UINT8 ubReason )
{
#ifdef JA2BETAVERSION
if (is_networked) {
CHAR tmpMPDbgString[512];
sprintf(tmpMPDbgString,"ExplosiveDamageStructureAtGridNo ( sGridNo : %i , sWoundAmt : %i , uiDist : %i , fRecompMoveCosts : %i , fOnlyWalls : %i , SubsMulTilTransDmg : %i , ubOwner : %i , bLevel : %i )\n", sGridNo, sWoundAmt , uiDist, (int)*pfRecompileMovementCosts , (int)fOnlyWalls , (int)fSubSequentMultiTilesTransitionDamage , ubOwner , bLevel );
MPDebugMsg(tmpMPDbgString);
}
#endif
INT16 sX, sY;
STRUCTURE *pBase, *pWallStruct, *pAttached, *pAttachedBase;
LEVELNODE *pNode = NULL, *pNewNode = NULL, *pAttachedNode;
INT32 sNewGridNo, sStructGridNo;
INT16 sNewIndex, sSubIndex;
UINT16 usObjectIndex, usTileIndex;
UINT8 ubNumberOfTiles, ubLoop;
DB_STRUCTURE_TILE ** ppTile;
INT8 bDestructionPartner=-1;
INT8 bDamageReturnVal;
INT8 docontinue(0);
UINT32 uiTileType;
INT32 sBaseGridNo;
BOOLEAN fExplosive;
// ATE: Check for O3 statue for special damage..
// note we do this check every time explosion goes off in game, but it's
// an effiecnent check...
if ( DoesO3SectorStatueExistHere( sGridNo ) && uiDist <= 1 )
{
ChangeO3SectorStatue( TRUE );
return( 1 );
}
#ifdef JA2UB
//JA25 UB
//should we replace the mine entrance graphic
if( IsMineEntranceInSectorI13AtThisGridNo( sGridNo ) && ubOwner == NOBODY )
{
//Yup, replace it
ReplaceMineEntranceGraphicWithCollapsedEntrance();
}
//ja25 ub
//Handle Explosions in the tunnel sectors
HandleExplosionsInTunnelSector( sGridNo );
#endif
// Get xy
sX = CenterX( sGridNo );
sY = CenterY( sGridNo );
// ATE: Continue if we are only looking for walls
if ( fOnlyWalls && !( pCurrent->fFlags & STRUCTURE_WALLSTUFF ) )
{
return( 1 );
}
if ( bLevel > 0 )
{
return( 1 );
}
// Is this a corpse?
if ( ( pCurrent->fFlags & STRUCTURE_CORPSE ) && gGameSettings.fOptions[ TOPTION_BLOOD_N_GORE ] && sWoundAmt > 10 )
{
// Spray corpse in a fine mist....
if ( uiDist <= 1 )
{
// Remove corpse...
VaporizeCorpse( sGridNo, bLevel, pCurrent->usStructureID );
}
}
else if ( !( pCurrent->fFlags & STRUCTURE_PERSON ) )
{
// Damage structure!
if ( ( bDamageReturnVal = DamageStructure( pCurrent, (UINT8)sWoundAmt, STRUCTURE_DAMAGE_EXPLOSION, sGridNo, sX, sY, NOBODY ) ) != 0 )
{
#ifdef JA2UB
//Ja25 ub
//are we exploding the Fan in the power gen facility
if( IsFanGraphicInSectorAtThisGridNo( sGridNo ) )
{
HandleDestructionOfPowerGenFan();
}
#endif
pBase = FindBaseStructure( pCurrent );
sBaseGridNo = pBase->sGridNo;
// if the structure is openable, destroy all items there
// Flugente: only if the structure has been entirely destroyed (why would we destroy what's inside if the structure still exists?)
if ( bDamageReturnVal == 1 &&
pBase->fFlags & STRUCTURE_OPENABLE &&
!(pBase->fFlags & STRUCTURE_DOOR ) )
{
// explosions destroy items inside
if ( ubReason == STRUCTURE_DAMAGE_EXPLOSION )
RemoveAllUnburiedItems( pBase->sGridNo, bLevel );
}
fExplosive = ( ( pCurrent->fFlags & STRUCTURE_EXPLOSIVE ) != 0 );
// Get LEVELNODE for struct and remove!
pNode = FindLevelNodeBasedOnStructure( pBase->sGridNo, pBase );
// ATE: if we have completely destroyed a structure,
// and this structure should have a in-between explosion partner,
// make damage code 2 - which means only damaged - the normal explosion
// spreading will cause it do use the proper peices..
if ( bDamageReturnVal == 1 && pBase->pDBStructureRef->pDBStructure->bDestructionPartner < 0 )
{
bDamageReturnVal = 2;
}
if ( bDamageReturnVal == 1 )
{
docontinue = 1;
}
// Check for a damaged looking graphic...
else if ( bDamageReturnVal == 2 )
{
if ( pBase->pDBStructureRef->pDBStructure->bDestructionPartner < 0 )
{
// We swap to another graphic!
// It's -ve and 1-based, change to +ve, 1 based
bDestructionPartner = ( -1 * pBase->pDBStructureRef->pDBStructure->bDestructionPartner );
GetTileType( pNode->usIndex, &uiTileType );
docontinue = 2;
}
}
if ( docontinue )
{
// Remove the beast!
while ( (*ppNextCurrent) != NULL && (*ppNextCurrent)->usStructureID == pCurrent->usStructureID )
{
// the next structure will also be deleted so we had better
// skip past it!
(*ppNextCurrent) = (*ppNextCurrent)->pNext;
}
// Replace with explosion debris if there are any....
// ( and there already sin;t explosion debris there.... )
if ( pBase->pDBStructureRef->pDBStructure->bDestructionPartner > 0 )
{
// Alrighty add!
// Add to every gridno structure is in
ubNumberOfTiles = pBase->pDBStructureRef->pDBStructure->ubNumberOfTiles;
ppTile = pBase->pDBStructureRef->ppTile;
bDestructionPartner = pBase->pDBStructureRef->pDBStructure->bDestructionPartner;
// OK, destrcution index is , as default, the partner, until we go over the first set of explsion
// debris...
if ( bDestructionPartner > 39 )
{
GetTileIndexFromTypeSubIndex( SECONDEXPLDEBRIS, (INT8)( bDestructionPartner - 40 ), &usTileIndex );
}
else
{
GetTileIndexFromTypeSubIndex( FIRSTEXPLDEBRIS, bDestructionPartner, &usTileIndex );
}
// Free all the non-base tiles; the base tile is at pointer 0
for (ubLoop = BASE_TILE; ubLoop < ubNumberOfTiles; ubLoop++)
{
if ( !(ppTile[ ubLoop ]->fFlags & TILE_ON_ROOF ) )
{
sStructGridNo = AddPosRelToBase(pBase->sGridNo, ppTile[ubLoop]);
// there might be two structures in this tile, one on each level, but we just want to
// delete one on each pass
if ( !TypeRangeExistsInObjectLayer( sStructGridNo, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS, &usObjectIndex ) )
{
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
AddObjectToHead( sStructGridNo, (UINT16)(usTileIndex + Random( 3 ) ) );
ApplyMapChangesToMapTempFile( FALSE );
}
}
}
// IF we are a wall, add debris for the other side
if ( pCurrent->fFlags & STRUCTURE_WALLSTUFF )
{
switch( pCurrent->ubWallOrientation )
{
case OUTSIDE_TOP_LEFT:
case INSIDE_TOP_LEFT:
sStructGridNo = NewGridNo( pBase->sGridNo, DirectionInc( SOUTH ) );
if ( !TypeRangeExistsInObjectLayer( sStructGridNo, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS, &usObjectIndex ) )
{
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
AddObjectToHead( sStructGridNo, (UINT16)(usTileIndex + Random( 3 ) ) );
ApplyMapChangesToMapTempFile( FALSE );
}
break;
case OUTSIDE_TOP_RIGHT:
case INSIDE_TOP_RIGHT:
sStructGridNo = NewGridNo( pBase->sGridNo, DirectionInc( EAST ) );
if ( !TypeRangeExistsInObjectLayer( sStructGridNo, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS, &usObjectIndex ) )
{
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
AddObjectToHead( sStructGridNo, (UINT16)(usTileIndex + Random( 3 ) ) );
ApplyMapChangesToMapTempFile( FALSE );
}
break;
}
}
}
// Else look for fences, walk along them to change to destroyed peices...
else if ( pCurrent->fFlags & STRUCTURE_FENCE )
{
// walk along based on orientation
switch( pCurrent->ubWallOrientation )
{
case OUTSIDE_TOP_RIGHT:
case INSIDE_TOP_RIGHT:
sStructGridNo = NewGridNo( pBase->sGridNo, DirectionInc( SOUTH ) );
HandleFencePartnerCheck( sStructGridNo );
sStructGridNo = NewGridNo( pBase->sGridNo, DirectionInc( NORTH ) );
HandleFencePartnerCheck( sStructGridNo );
break;
case OUTSIDE_TOP_LEFT:
case INSIDE_TOP_LEFT:
sStructGridNo = NewGridNo( pBase->sGridNo, DirectionInc( EAST ) );
HandleFencePartnerCheck( sStructGridNo );
sStructGridNo = NewGridNo( pBase->sGridNo, DirectionInc( WEST ) );
HandleFencePartnerCheck( sStructGridNo );
break;
}
}
// OK, Check if this is a wall, then search and change other walls based on this
if ( pCurrent->fFlags & STRUCTURE_WALLSTUFF )
{
// ATE
// Remove any decals in tile....
// Use tile database for this as apposed to stuct data
RemoveAllStructsOfTypeRange( pBase->sGridNo, FIRSTWALLDECAL, FOURTHWALLDECAL );
RemoveAllStructsOfTypeRange( pBase->sGridNo, FIFTHWALLDECAL, EIGTHWALLDECAL );
// Alrighty, now do this
// Get orientation
// based on orientation, go either x or y dir
// check for wall in both _ve and -ve directions
// if found, replace!
switch( pCurrent->ubWallOrientation )
{
case OUTSIDE_TOP_LEFT:
case INSIDE_TOP_LEFT:
// Move WEST
sNewGridNo = NewGridNo( pBase->sGridNo, DirectionInc( WEST ) );
pNewNode = GetWallLevelNodeAndStructOfSameOrientationAtGridNo( sNewGridNo, pCurrent->ubWallOrientation, &pWallStruct );
if ( pNewNode != NULL )
{
if ( pWallStruct->fFlags & STRUCTURE_WALL )
{
if ( pCurrent->ubWallOrientation == OUTSIDE_TOP_LEFT )
{
sSubIndex = 48;
}
else
{
sSubIndex = 52;
}
// Replace!
GetTileIndexFromTypeSubIndex( gTileDatabase[ pNewNode->usIndex ].fType, sSubIndex, (UINT16 *)&sNewIndex );
//Set a flag indicating that the following changes are to go the the maps temp file
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( sNewGridNo, pNewNode );
AddWallToStructLayer( sNewGridNo, sNewIndex, TRUE );
ApplyMapChangesToMapTempFile( FALSE );
}
}
// Move in EAST
sNewGridNo = NewGridNo( pBase->sGridNo, DirectionInc( EAST ) );
pNewNode = GetWallLevelNodeAndStructOfSameOrientationAtGridNo( sNewGridNo, pCurrent->ubWallOrientation, &pWallStruct );
if ( pNewNode != NULL )
{
if ( pWallStruct->fFlags & STRUCTURE_WALL )
{
if ( pCurrent->ubWallOrientation == OUTSIDE_TOP_LEFT )
{
sSubIndex = 49;
}
else
{
sSubIndex = 53;
}
// Replace!
GetTileIndexFromTypeSubIndex( gTileDatabase[ pNewNode->usIndex ].fType, sSubIndex, (UINT16 *)&sNewIndex );
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( sNewGridNo, pNewNode );
AddWallToStructLayer( sNewGridNo, sNewIndex, TRUE );
ApplyMapChangesToMapTempFile( FALSE );
}
}
// look for attached structures in same tile
sNewGridNo = pBase->sGridNo;
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_LEFT_WALL );
while (pAttached)
{
pAttachedBase = FindBaseStructure( pAttached );
if (pAttachedBase)
{
// Remove the beast!
while ( (*ppNextCurrent) != NULL && (*ppNextCurrent)->usStructureID == pAttachedBase->usStructureID )
{
// the next structure will also be deleted so we had better
// skip past it!
(*ppNextCurrent) = (*ppNextCurrent)->pNext;
}
pAttachedNode = FindLevelNodeBasedOnStructure( pAttachedBase->sGridNo, pAttachedBase );
if (pAttachedNode)
{
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( pAttachedBase->sGridNo, pAttachedNode );
ApplyMapChangesToMapTempFile( FALSE );
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
// search for another, from the start of the list
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_LEFT_WALL );
}
// Move in SOUTH, looking for attached structures to remove
sNewGridNo = NewGridNo( pBase->sGridNo, DirectionInc( SOUTH ) );
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_LEFT_WALL );
while (pAttached)
{
pAttachedBase = FindBaseStructure( pAttached );
if (pAttachedBase)
{
pAttachedNode = FindLevelNodeBasedOnStructure( pAttachedBase->sGridNo, pAttachedBase );
if (pAttachedNode)
{
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( pAttachedBase->sGridNo, pAttachedNode );
ApplyMapChangesToMapTempFile( FALSE );
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
// search for another, from the start of the list
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_LEFT_WALL );
}
break;
case OUTSIDE_TOP_RIGHT:
case INSIDE_TOP_RIGHT:
// Move in NORTH
sNewGridNo = NewGridNo( pBase->sGridNo, DirectionInc( NORTH ) );
pNewNode = GetWallLevelNodeAndStructOfSameOrientationAtGridNo( sNewGridNo, pCurrent->ubWallOrientation, &pWallStruct );
if ( pNewNode != NULL )
{
if ( pWallStruct->fFlags & STRUCTURE_WALL )
{
if ( pCurrent->ubWallOrientation == OUTSIDE_TOP_RIGHT )
{
sSubIndex = 51;
}
else
{
sSubIndex = 55;
}
// Replace!
GetTileIndexFromTypeSubIndex( gTileDatabase[ pNewNode->usIndex ].fType, sSubIndex, (UINT16 *)&sNewIndex );
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( sNewGridNo, pNewNode );
AddWallToStructLayer( sNewGridNo, sNewIndex, TRUE );
ApplyMapChangesToMapTempFile( FALSE );
}
}
// Move in SOUTH
sNewGridNo = NewGridNo( pBase->sGridNo, DirectionInc( SOUTH ) );
pNewNode = GetWallLevelNodeAndStructOfSameOrientationAtGridNo( sNewGridNo, pCurrent->ubWallOrientation, &pWallStruct );
if ( pNewNode != NULL )
{
if ( pWallStruct->fFlags & STRUCTURE_WALL )
{
if ( pCurrent->ubWallOrientation == OUTSIDE_TOP_RIGHT )
{
sSubIndex = 50;
}
else
{
sSubIndex = 54;
}
// Replace!
GetTileIndexFromTypeSubIndex( gTileDatabase[ pNewNode->usIndex ].fType, sSubIndex, (UINT16 *)&sNewIndex );
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( sNewGridNo, pNewNode );
AddWallToStructLayer( sNewGridNo, sNewIndex, TRUE );
ApplyMapChangesToMapTempFile( FALSE );
}
}
// looking for attached structures to remove in base tile
sNewGridNo = pBase->sGridNo;
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_RIGHT_WALL );
while (pAttached)
{
pAttachedBase = FindBaseStructure( pAttached );
if (pAttachedBase)
{
pAttachedNode = FindLevelNodeBasedOnStructure( pAttachedBase->sGridNo, pAttachedBase );
if (pAttachedNode)
{
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( pAttachedBase->sGridNo, pAttachedNode );
ApplyMapChangesToMapTempFile( FALSE );
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
// search for another, from the start of the list
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_RIGHT_WALL );
}
// Move in EAST, looking for attached structures to remove
sNewGridNo = NewGridNo( pBase->sGridNo, DirectionInc( EAST ) );
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_RIGHT_WALL );
while (pAttached)
{
pAttachedBase = FindBaseStructure( pAttached );
if (pAttachedBase)
{
pAttachedNode = FindLevelNodeBasedOnStructure( pAttachedBase->sGridNo, pAttachedBase );
if (pAttachedNode)
{
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( pAttachedBase->sGridNo, pAttachedNode );
ApplyMapChangesToMapTempFile( FALSE );
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
}
else
{
// error!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Problems removing structure attached to wall at %d", sNewGridNo );
#endif
break;
}
// search for another, from the start of the list
pAttached = FindStructure( sNewGridNo, STRUCTURE_ON_RIGHT_WALL );
}
break;
}
// CJC, Sept 16: if we destroy any wall of the brothel, make Kingpin's men hostile!
if ( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_C && gbWorldSectorZ == 0 )
{
//DBrot: More Rooms
//UINT8 ubRoom;
UINT16 usRoom;
BOOLEAN fInRoom;
fInRoom = InARoom( sGridNo, &usRoom );
if ( !fInRoom )
{
// try to south
fInRoom = InARoom( sGridNo + DirectionInc( SOUTH ) , &usRoom );
if ( !fInRoom )
{
// try to east
fInRoom = InARoom( sGridNo + DirectionInc( EAST ) , &usRoom );
}
}
if ( fInRoom && IN_BROTHEL( usRoom ) )
{
CivilianGroupChangesSides( KINGPIN_CIV_GROUP );
}
}
}
// OK, we need to remove the water from the fountain
// Lots of HARD CODING HERE :(
// Get tile type
GetTileType( pNode->usIndex, &uiTileType );
// Check if we are a fountain!
if ( _stricmp( gTilesets[ giCurrentTilesetID ].TileSurfaceFilenames[ uiTileType ], "fount1.sti" ) == 0 )
{
// Yes we are!
// Remove water....
ApplyMapChangesToMapTempFile( TRUE );
GetTileIndexFromTypeSubIndex( uiTileType, 1, (UINT16 *)&sNewIndex );
RemoveStruct( sBaseGridNo, sNewIndex );
RemoveStruct( sBaseGridNo, sNewIndex );
GetTileIndexFromTypeSubIndex( uiTileType, 2, (UINT16 *)&sNewIndex );
RemoveStruct( sBaseGridNo, sNewIndex );
RemoveStruct( sBaseGridNo, sNewIndex );
GetTileIndexFromTypeSubIndex( uiTileType, 3, (UINT16 *)&sNewIndex );
RemoveStruct( sBaseGridNo, sNewIndex );
RemoveStruct( sBaseGridNo, sNewIndex );
ApplyMapChangesToMapTempFile( FALSE );
}
// Remove any interactive tiles we could be over!
BeginCurInteractiveTileCheck( INTILE_CHECK_SELECTIVE );
if ( pCurrent->fFlags & STRUCTURE_WALLSTUFF )
{
RecompileLocalMovementCostsForWall( pBase->sGridNo, pBase->ubWallOrientation );
}
// Remove!
//Set a flag indicating that the following changes are to go the the maps, temp file
ApplyMapChangesToMapTempFile( TRUE );
RemoveStructFromLevelNode( pBase->sGridNo, pNode );
ApplyMapChangesToMapTempFile( FALSE );
// OK, if we are to swap structures, do it now...
if ( docontinue == 2 )
{
// We have a levelnode...
// Get new index for new grpahic....
GetTileIndexFromTypeSubIndex( uiTileType, bDestructionPartner, &usTileIndex );
ApplyMapChangesToMapTempFile( TRUE );
AddStructToHead( sBaseGridNo, usTileIndex );
ApplyMapChangesToMapTempFile( FALSE );
}
// Rerender world!
// Reevaluate world movement costs, reduncency!
gTacticalStatus.uiFlags |= NOHIDE_REDUNDENCY;
// FOR THE NEXT RENDER LOOP, RE-EVALUATE REDUNDENT TILES
InvalidateWorldRedundency( );
SetRenderFlags(RENDER_FLAG_FULL);
// Handle properly lighting the structure that was modified
if( gbWorldSectorZ == 0 )
{
// ATE: make sure the lighting on the graphic gets updateds
LightSetBaseLevel( GetTimeOfDayAmbientLightLevel() );
}
else
{
LightSetBaseLevel( LightGetAmbient() );
}
// Movement costs!
( *pfRecompileMovementCosts ) = TRUE;
{
// Make secondary explosion if eplosive....
if ( fExplosive )
{
InternalIgniteExplosion( ubOwner, CenterX( sBaseGridNo ), CenterY( sBaseGridNo ), 0, sBaseGridNo, STRUCTURE_EXPLOSION, FALSE, bLevel );
}
}
if ( docontinue == 2 )
{
return( 1 );
}
}
// 2 is NO DAMAGE
return( 2 );
}
}
return( 1 );
}
STRUCTURE *gStruct;
void ExplosiveDamageGridNo( INT32 sGridNo, INT16 sWoundAmt, UINT32 uiDist, BOOLEAN *pfRecompileMovementCosts,
BOOLEAN fOnlyWalls, INT8 bMultiStructSpecialFlag, BOOLEAN fSubSequentMultiTilesTransitionDamage, UINT8 ubOwner, INT8 bLevel,
UINT8 ubReason )
{
#ifdef JA2BETAVERSION
if (is_networked) {
CHAR tmpMPDbgString[512];
sprintf(tmpMPDbgString,"ExplosiveDamageGridNo ( sGridNo : %i , sWoundAmt : %i , uiDist : %i , fRecompileMoveCosts : %i , fOnlyWalls : %i , MultiStructSpecialFlag : %i ,fSubsequentMultiTilesTransDmg : %i , ubOwner : %i , bLevel : %i )\n", sGridNo, sWoundAmt ,uiDist, (int)*pfRecompileMovementCosts , (int)fOnlyWalls , bMultiStructSpecialFlag , (int)fSubSequentMultiTilesTransitionDamage , ubOwner , bLevel );
MPDebugMsg(tmpMPDbgString);
}
#endif
STRUCTURE *pCurrent, *pNextCurrent, *pStructure;
STRUCTURE *pBaseStructure;
INT16 sDesiredLevel;
DB_STRUCTURE_TILE **ppTile = NULL;
UINT8 ubLoop, ubLoop2;
INT32 sNewGridNo, sNewGridNo2, sBaseGridNo = NOWHERE;
BOOLEAN fToBreak = FALSE;
BOOLEAN fMultiStructure = FALSE;
UINT8 ubNumberOfTiles = 0xff;
BOOLEAN fMultiStructSpecialFlag = FALSE;
BOOLEAN fExplodeDamageReturn = FALSE;
// Based on distance away, damage any struct at this gridno
// OK, loop through structures and damage!
pCurrent = gpWorldLevelData[ sGridNo ].pStructureHead;
sDesiredLevel = STRUCTURE_ON_GROUND;
// This code gets a little hairy because
// (1) we might need to destroy the currently-examined structure
while (pCurrent != NULL)
{
// ATE: These are for the checks below for multi-structs....
pBaseStructure = FindBaseStructure( pCurrent );
if ( pBaseStructure )
{
sBaseGridNo = pBaseStructure->sGridNo;
fMultiStructure = ( ( pBaseStructure->fFlags & STRUCTURE_MULTI ) != 0 );
if ( bMultiStructSpecialFlag == -1 )
{
// Set it!
bMultiStructSpecialFlag = ( ( pBaseStructure->fFlags & STRUCTURE_SPECIAL ) != 0 );
}
if ( pBaseStructure->fFlags & STRUCTURE_EXPLOSIVE )
{
// ATE: Set hit points to zero....
pBaseStructure->ubHitPoints = 0;
}
if ( pBaseStructure->pDBStructureRef )
{
ubNumberOfTiles = pBaseStructure->pDBStructureRef->pDBStructure->ubNumberOfTiles;
ppTile = (DB_STRUCTURE_TILE **)MemAlloc( sizeof( DB_STRUCTURE_TILE* ) * ubNumberOfTiles );
memcpy( ppTile, pBaseStructure->pDBStructureRef->ppTile, sizeof( DB_STRUCTURE_TILE* ) * ubNumberOfTiles );
}
else
{
ubNumberOfTiles = 0;
ppTile = 0;
}
}
else
{
fMultiStructure = FALSE;
sBaseGridNo = 0;
ubNumberOfTiles = 0;
ppTile = 0;
}
pNextCurrent = pCurrent->pNext;
gStruct = pNextCurrent;
// Check level!
if (pCurrent->sCubeOffset == sDesiredLevel )
{
fExplodeDamageReturn = ExplosiveDamageStructureAtGridNo( pCurrent, &pNextCurrent, sGridNo, sWoundAmt, uiDist, pfRecompileMovementCosts, fOnlyWalls, 0, ubOwner, bLevel, ubReason );
// Are we overwriting damage due to multi-tile...?
if ( fExplodeDamageReturn )
{
if ( fSubSequentMultiTilesTransitionDamage == 2)
{
fExplodeDamageReturn = 2;
}
else
{
fExplodeDamageReturn = 1;
}
}
if ( !fExplodeDamageReturn )
{
fToBreak = TRUE;
}
//}
// 0verhaul: The following was combined with the previous code block. I don't think they intended to execute this
// part unless fExplodeDamageReturn was actually set. When it was being executed, tossing a grenade just behind the
// plane in Drassen, for instance, would cause an infinite recursion in this code. The reason is that the plane's
// armor is (amazingly enough) stronger than a grenade blast can even damage. This code here seems to rely on the
// structure in question being destroyed by the blast since it indiscriminently recurses on neighbors, creating a
// ping pong on two adjacent parts of the plane. Probably the reason this was not found before is that fExplodeDamageReturn
// was uninitialized before and usually was non-zero. Now it is initialized to false.
// OK, for multi-structs...
// AND we took damage...
if ( fMultiStructure && !fOnlyWalls && fExplodeDamageReturn == 0 )
{
// ATE: Don't after first attack...
if ( uiDist > 1 )
{
if ( pBaseStructure )
{
MemFree( ppTile );
}
return;
}
{
for ( ubLoop = BASE_TILE; ubLoop < ubNumberOfTiles; ubLoop++)
{
sNewGridNo = AddPosRelToBase(sBaseGridNo, ppTile[ubLoop]);
// look in adjacent tiles
for ( ubLoop2 = 0; ubLoop2 < NUM_WORLD_DIRECTIONS; ubLoop2++ )
{
sNewGridNo2 = NewGridNo( sNewGridNo, DirectionInc( ubLoop2 ) );
if ( sNewGridNo2 != sNewGridNo && sNewGridNo2 != sGridNo )
{
pStructure = FindStructure( sNewGridNo2, STRUCTURE_MULTI );
if ( pStructure )
{
fMultiStructSpecialFlag = ( ( pStructure->fFlags & STRUCTURE_SPECIAL ) != 0 );
if ( ( bMultiStructSpecialFlag == fMultiStructSpecialFlag ) )
{
// If we just damaged it, use same damage value....
if ( fMultiStructSpecialFlag )
{
ExplosiveDamageGridNo( sNewGridNo2, sWoundAmt, uiDist, pfRecompileMovementCosts, fOnlyWalls, bMultiStructSpecialFlag, 1, ubOwner, bLevel, ubReason );
}
else
{
ExplosiveDamageGridNo( sNewGridNo2, sWoundAmt, uiDist, pfRecompileMovementCosts, fOnlyWalls, bMultiStructSpecialFlag, 2, ubOwner, bLevel, ubReason );
}
{
InternalIgniteExplosion( ubOwner, CenterX( sNewGridNo2 ), CenterY( sNewGridNo2 ), 0, sNewGridNo2, RDX, FALSE, bLevel );
}
fToBreak = TRUE;
}
}
}
}
}
}
if ( fToBreak )
{
break;
}
}
}
if ( pBaseStructure )
{
MemFree( ppTile );
}
pCurrent = pNextCurrent;
}
}
BOOLEAN DamageSoldierFromBlast( UINT8 ubPerson, UINT8 ubOwner, INT32 sBombGridNo, INT16 sWoundAmt, INT16 sBreathAmt, UINT32 uiDist, UINT16 usItem, INT16 sSubsequent , BOOL fFromRemoteClient )
{
// OJW - 20091028
if (is_networked && is_client)
{
SOLDIERTYPE* pSoldier = MercPtrs[ubPerson];
if (pSoldier != NULL)
{
// only the owner of a merc may send damage (as this takes into account equipped armor)
if (IsOurSoldier(pSoldier) || (pSoldier->bTeam == 1 && is_server) && !fFromRemoteClient)
{
// let this function proceed, we will send damage towards the end
}
else if (!fFromRemoteClient)
{
// skip executing locally because we want the random number generator to be aligned
// with the client that spawns set off the explosion/grenade/whatever
return FALSE;
}
}
#ifdef JA2BETAVERSION
CHAR tmpMPDbgString[512];
sprintf(tmpMPDbgString,"DamageSoldierFromBlast ( ubPerson : %i , ubOwner : %i , sBombGridNo : %i , sWoundAmt : %i , sBreathAmt : %i , uiDist : %i , usItem : %i , sSubs : %i , fFromRemoteClient : %i )\n",ubPerson, ubOwner , sBombGridNo , sWoundAmt , sBreathAmt , uiDist , usItem , sSubsequent , fFromRemoteClient );
MPDebugMsg(tmpMPDbgString);
#endif
}
SOLDIERTYPE *pSoldier;
INT16 sNewWoundAmt = 0;
UINT8 ubDirection;
UINT8 ubSpecial = 0;
BOOLEAN fInBuilding = InBuilding(sBombGridNo);
BOOLEAN fFlashbang = Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_FLASHBANG;
UINT16 usHalfExplosionRadius;
pSoldier = MercPtrs[ ubPerson ]; // someone is here, and they're gonna get hurt
if (!pSoldier->bActive || !pSoldier->bInSector || !pSoldier->stats.bLife )
return( FALSE );
if ( pSoldier->ubMiscSoldierFlags & SOLDIER_MISC_HURT_BY_EXPLOSION )
{
// don't want to damage the guy twice
return( FALSE );
}
// Flugente: do we have a riot shield equipped?
if ( pSoldier->IsRiotShieldEquipped( ) )
{
UINT8 attackdir_inverse = GetDirectionToGridNoFromGridNo( pSoldier->sGridNo, sBombGridNo );
// if the shield faces the direction of the attack, block it
if ( attackdir_inverse == pSoldier->ubDirection || attackdir_inverse == gOneCCDirection[pSoldier->ubDirection] || attackdir_inverse == gOneCDirection[pSoldier->ubDirection] )
{
INT32 damage = sWoundAmt;
INT32 breathdamage = sBreathAmt;
DamageRiotShield( pSoldier, damage, breathdamage );
sWoundAmt = damage;
sBreathAmt = breathdamage;
if ( sWoundAmt == 0 && sBreathAmt == 0 )
{
return FALSE;
}
}
}
// Lesh: if flashbang
// check if soldier is outdoor and situated farther that half explosion radius and not underground
usHalfExplosionRadius = Explosive[Item[usItem].ubClassIndex].ubRadius / 2;
if ( fFlashbang && !gbWorldSectorZ && !fInBuilding && uiDist > usHalfExplosionRadius )
{
// HEADROCK HAM 3.3: Flashbang at half distance causes up to 6 suppression points. Roughly equivalent of being
// "lightly" shot at.
if (gGameExternalOptions.usExplosionSuppressionEffect > 0)
{
pSoldier->ubSuppressionPoints += (PreRandom(6) * gGameExternalOptions.usExplosionSuppressionEffect) / 100;
}
// then no effect
return( FALSE );
}
// Direction to center of explosion
ubDirection = (UINT8)GetDirectionFromGridNo( sBombGridNo, pSoldier );
// Increment attack counter...
// gTacticalStatus.ubAttackBusyCount++;
DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Incrementing Attack: Explosion dishing out damage, Count now %d", gTacticalStatus.ubAttackBusyCount ) );
DebugAttackBusy( String("Explosion dishing out damage to %d\n", pSoldier->ubID) );
sNewWoundAmt = sWoundAmt - __min( sWoundAmt, 35 ) * ArmourVersusExplosivesPercent( pSoldier ) / 100;
if ( sNewWoundAmt < 0 )
{
sNewWoundAmt = 0;
}
////////////////////////////////////////////////////////////////////////////////////
// SANDRO - STOMP traits
else
{
if ( ubOwner < TOTAL_SOLDIERS && (MercPtrs[ ubOwner ] != NULL) && gGameOptions.fNewTraitSystem)
{
// Demolitions damage bonus with bombs and mines
if ( HAS_SKILL_TRAIT( MercPtrs[ ubOwner ], DEMOLITIONS_NT ) &&
Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL && Item[usItem].usItemClass == IC_BOMB &&
(!Item[usItem].attachment || Item[usItem].mine ))
{
sNewWoundAmt = (INT16)(sNewWoundAmt * (100 + gSkillTraitValues.ubDEDamageOfBombsAndMines) / 100.0f + 0.5f);
}
// Heavy Weapons trait bonus damage to tanks
if ( HAS_SKILL_TRAIT( MercPtrs[ubOwner], HEAVY_WEAPONS_NT ) && (ARMED_VEHICLE( pSoldier ) || ENEMYROBOT( pSoldier )) &&
Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL )
{
sNewWoundAmt = (INT16)(sNewWoundAmt * (100 + gSkillTraitValues.ubHWDamageTanksBonusPercent * NUM_SKILL_TRAITS( MercPtrs[ ubOwner ], HEAVY_WEAPONS_NT )) / 100.0f + 0.5f); // +30%
}
// Heavy Weapons trait bonus damage with rocket, grenade launchers and mortar
else if ( HAS_SKILL_TRAIT( MercPtrs[ ubOwner ], HEAVY_WEAPONS_NT ) &&
Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL &&
((Item[usItem].usItemClass == IC_BOMB && Item[usItem].attachment && !Item[usItem].mine ) || // mortar shells
(Item[usItem].usItemClass == IC_GRENADE && (Item[usItem].glgrenade || Item[usItem].electronic) ) || // rockets for rocketlaunchers (I haven't found any other way)
(Item[usItem].usItemClass == IC_LAUNCHER ) || Item[usItem].rocketlauncher || Item[usItem].singleshotrocketlauncher ) )
{
sNewWoundAmt = (INT16)(sNewWoundAmt * (100 + gSkillTraitValues.ubHWDamageBonusPercentForHW * NUM_SKILL_TRAITS( MercPtrs[ ubOwner ], HEAVY_WEAPONS_NT )) / 100.0f + 0.5f); // +15%
}
}
// adjust damage resistance of TANKS
if ( TANK( pSoldier ) && gGameOptions.fNewTraitSystem )
{
sNewWoundAmt = (INT16)(sNewWoundAmt * (100 - gSkillTraitValues.bTanksDamageResistanceModifier) / 100);
// another half of this for ordinary grenades
if ( (( Item[usItem].usItemClass == IC_GRENADE ) || Item[usItem].glgrenade ) && !Item[usItem].electronic )
sNewWoundAmt = (INT16)(sNewWoundAmt * (100 - (gSkillTraitValues.bTanksDamageResistanceModifier / 2)) / 100);
}
// Flugente: moved the damage calculation into a separate function
INT32 sDamageResistance = pSoldier->GetDamageResistance( FALSE, FALSE );
sNewWoundAmt = max( 0, (INT16)(sNewWoundAmt * (100 - sDamageResistance) / 100) );
// Flugente: this seems a bit weird, but is consistent with the previous code - we do not receive stat damage if we have positive damage resistance
if ( sDamageResistance > 0 )
;
// we can loose stats due to being hit by the blast
else if ( gGameOptions.fNewTraitSystem && Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL &&
!AM_A_ROBOT( pSoldier ) && !(pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) && !ARMED_VEHICLE( pSoldier ) && !ENEMYROBOT( pSoldier ) &&
sNewWoundAmt > 2 && sNewWoundAmt < pSoldier->stats.bLife )
{
if ( PreRandom( sNewWoundAmt ) > gSkillTraitValues.ubDamageNeededToLoseStats )
{
UINT8 ubStatLoss = PreRandom( sNewWoundAmt ) + 1;
UINT8 ubPickStat = PreRandom( 20 );
if (ubPickStat < 3 ) // 15% chance to lose Wisdom
{
if (ubStatLoss >= pSoldier->stats.bWisdom)
{
ubStatLoss = pSoldier->stats.bWisdom - 1;
}
if ( ubStatLoss > 0 )
{
pSoldier->stats.bWisdom -= ubStatLoss;
pSoldier->ubCriticalStatDamage[DAMAGED_STAT_WISDOM] += ubStatLoss;
if (pSoldier->ubProfile != NO_PROFILE)
{
gMercProfiles[ pSoldier->ubProfile ].bWisdom = pSoldier->stats.bWisdom;
}
if (pSoldier->name[0] && pSoldier->bVisible == TRUE)
{
// make stat RED for a while...
pSoldier->timeChanges.uiChangeWisdomTime = GetJA2Clock();
pSoldier->usValueGoneUp &= ~( WIS_INCREASE );
if (ubStatLoss == 1)
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_WISDOM], pSoldier->GetName() );
}
else
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_WISDOM], pSoldier->GetName(), ubStatLoss );
}
}
}
}
else if (ubPickStat < 7 ) // 20% chance to lose Dexterity
{
if (ubStatLoss >= pSoldier->stats.bDexterity)
{
ubStatLoss = pSoldier->stats.bDexterity - 1;
}
if ( ubStatLoss > 0 )
{
pSoldier->stats.bDexterity -= ubStatLoss;
pSoldier->ubCriticalStatDamage[DAMAGED_STAT_DEXTERITY] += ubStatLoss;
if (pSoldier->ubProfile != NO_PROFILE)
{
gMercProfiles[ pSoldier->ubProfile ].bDexterity = pSoldier->stats.bDexterity;
}
if (pSoldier->name[0] && pSoldier->bVisible == TRUE)
{
// make stat RED for a while...
pSoldier->timeChanges.uiChangeDexterityTime = GetJA2Clock();
pSoldier->usValueGoneUp &= ~( DEX_INCREASE );
if (ubStatLoss == 1)
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_DEX], pSoldier->GetName() );
}
else
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_DEX], pSoldier->GetName(), ubStatLoss );
}
}
}
}
else if (ubPickStat < 11 ) // 20% chance to lose Strength
{
if (ubStatLoss >= pSoldier->stats.bStrength)
{
ubStatLoss = pSoldier->stats.bStrength - 1;
}
if ( ubStatLoss > 0 )
{
pSoldier->stats.bStrength -= ubStatLoss;
// added this for healing lost stats feature
pSoldier->ubCriticalStatDamage[DAMAGED_STAT_STRENGTH] += ubStatLoss;
if (pSoldier->ubProfile != NO_PROFILE)
{
gMercProfiles[ pSoldier->ubProfile ].bStrength = pSoldier->stats.bStrength;
}
if (pSoldier->name[0] && pSoldier->bVisible == TRUE)
{
// make stat RED for a while...
pSoldier->timeChanges.uiChangeStrengthTime = GetJA2Clock();
pSoldier->usValueGoneUp &= ~( STRENGTH_INCREASE );
if (ubStatLoss == 1)
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_STRENGTH], pSoldier->GetName() );
}
else
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_STRENGTH], pSoldier->GetName(), ubStatLoss );
}
}
}
}
else if (ubPickStat < 15 ) // 20% chance to lose Agility
{
if (ubStatLoss >= pSoldier->stats.bAgility)
{
ubStatLoss = pSoldier->stats.bAgility - 1;
}
if ( ubStatLoss > 0 )
{
pSoldier->stats.bAgility -= ubStatLoss;
// added this for healing lost stats feature
pSoldier->ubCriticalStatDamage[DAMAGED_STAT_AGILITY] += ubStatLoss;
if (pSoldier->ubProfile != NO_PROFILE)
{
gMercProfiles[ pSoldier->ubProfile ].bAgility = pSoldier->stats.bAgility;
}
if (pSoldier->name[0] && pSoldier->bVisible == TRUE)
{
// make stat RED for a while...
pSoldier->timeChanges.uiChangeAgilityTime = GetJA2Clock();
pSoldier->usValueGoneUp &= ~( AGIL_INCREASE );
if (ubStatLoss == 1)
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_AGIL], pSoldier->GetName() );
}
else
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_AGIL], pSoldier->GetName(), ubStatLoss );
}
}
}
}
else if (ubPickStat < 18 ) // 15% chance to lose Health
{
if (ubStatLoss >= (pSoldier->stats.bLifeMax - OKLIFE))
{
ubStatLoss = pSoldier->stats.bLifeMax - OKLIFE - 1;
}
if ( ubStatLoss > sNewWoundAmt)
{
ubStatLoss = (UINT8)sNewWoundAmt;
}
if ( ubStatLoss > 0 )
{
pSoldier->stats.bLifeMax -= ubStatLoss;
pSoldier->bBleeding -= ubStatLoss;
pSoldier->ubCriticalStatDamage[DAMAGED_STAT_HEALTH] += ubStatLoss;
if (pSoldier->ubProfile != NO_PROFILE)
{
gMercProfiles[ pSoldier->ubProfile ].bLifeMax = pSoldier->stats.bLifeMax;
}
if (pSoldier->name[0] && pSoldier->bVisible == TRUE)
{
// make stat RED for a while...
pSoldier->timeChanges.uiChangeDexterityTime = GetJA2Clock();
pSoldier->usValueGoneUp &= ~( HEALTH_INCREASE );
if (ubStatLoss == 1)
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_LOSES_ONE_POINT_MAX_HEALTH], pSoldier->GetName() );
}
else
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_LOSES_X_POINTS_MAX_HEALTH], pSoldier->GetName(), ubStatLoss );
}
}
}
}
else // 10% chance to be blinded
{
if (pSoldier->bBlindedCounter < ubStatLoss )
{
pSoldier->bBlindedCounter = ubStatLoss ;
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_BLINDED_BY_BLAST], pSoldier->GetName() );
}
}
// SANDRO - new merc records - times stat damaged
if ( ubStatLoss > 0 && pSoldier->ubProfile != NO_PROFILE )
gMercProfiles[ pSoldier->ubProfile ].records.usTimesStatDamaged++;
}
}
sNewWoundAmt = max(0, sNewWoundAmt);
}
//////////////////////////////////////////////////////////////////////////////////////
// SANDRO - new merc records - times wounded (blasted by explosion)
if ( ( sNewWoundAmt > 1 || sBreathAmt > 1000 ) && pSoldier->ubProfile != NO_PROFILE )
gMercProfiles[ pSoldier->ubProfile ].records.usTimesWoundedBlasted++;
////////////////////////////////////////////////////////////////////////////////////
// Lesh: flashbang does affect on soldier or not - check it
if ( (Item[usItem].usItemClass & IC_EXPLOSV) && fFlashbang )
{
ubSpecial = DetermineFlashbangEffect( pSoldier, ubDirection, fInBuilding);
}
// HEADROCK HAM 3.3: Explosions cause suppression based on distance.
if (gGameExternalOptions.usExplosionSuppressionEffect > 0)
{
pSoldier->ubSuppressionPoints += ((__max(0,((Explosive[Item[usItem].ubClassIndex].ubRadius * 3) - uiDist)))* gGameExternalOptions.usExplosionSuppressionEffect) / 100;
if (fFlashbang && (gbWorldSectorZ || fInBuilding) && (UINT16)uiDist <= usHalfExplosionRadius)
{
pSoldier->ubSuppressionPoints += (15 * gGameExternalOptions.usExplosionSuppressionEffect) / 100;
}
}
if (is_networked && is_client)
{
if (IsOurSoldier(pSoldier) || (pSoldier->bTeam == 1 && is_server) && !fFromRemoteClient)
{
// if it gets here then we can let the other clients know our merc took damage
send_explosivedamage( ubPerson , ubOwner , sBombGridNo , sNewWoundAmt , sBreathAmt , uiDist , usItem , sSubsequent );
}
}
// OJW - 20091028 - If from a remote client, use unadjusted damage amount
pSoldier->EVENT_SoldierGotHit( usItem, (fFromRemoteClient ? sWoundAmt : sNewWoundAmt) , sBreathAmt, ubDirection, (INT16)uiDist, ubOwner, ubSpecial, ANIM_CROUCH, sSubsequent, sBombGridNo );
pSoldier->ubMiscSoldierFlags |= SOLDIER_MISC_HURT_BY_EXPLOSION;
if ( ubOwner != NOBODY && MercPtrs[ ubOwner ]->bTeam == gbPlayerNum && pSoldier->bTeam != gbPlayerNum )
{
ProcessImplicationsOfPCAttack( MercPtrs[ ubOwner ], &pSoldier, REASON_EXPLOSION );
}
return( TRUE );
}
BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, INT16 sSubsequent, BOOLEAN fRecompileMovementCosts, INT16 sWoundAmt, INT16 sBreathAmt, UINT8 ubOwner , BOOL fFromRemoteClient )
{
// OJW - 20091028
if (is_networked && is_client)
{
// only the owner of a merc may send damage (as this takes into account equipped gas mask)
if (IsOurSoldier(pSoldier) || (pSoldier->bTeam == 1 && is_server) && !fFromRemoteClient)
{
// allow this function to proceed, we will send it later, when we are sure we take damage this turn and from this function call
}
else if (!fFromRemoteClient)
{
// skip executing locally because we want the random number generator to be aligned
// with the client that spawns set off the explosion/grenade/whatever
return FALSE;
}
#ifdef JA2BETAVERSION
CHAR tmpMPDbgString[512];
sprintf(tmpMPDbgString,"DishOutGasDamage ( ubSoldierID : %i , ubExplosiveType : %i , sSubsequent : %i , recompileMoveCosts : %i , sWoundAmt : %i , sBreathAmt : %i , ubOwner : %i , fRemote : %i)\n", pSoldier->ubID , pExplosive->ubType , sSubsequent , fRecompileMovementCosts , sWoundAmt , sBreathAmt , ubOwner , fFromRemoteClient );
MPDebugMsg(tmpMPDbgString);
#endif
}
FLOAT fGasDamageModifier = 1.0;
FLOAT fGasBreathDamageModifier = 1.0;
INT8 bPosOfMask = NO_SLOT;
if (!pSoldier->bActive || !pSoldier->bInSector || !pSoldier->stats.bLife || AM_A_ROBOT( pSoldier ) || ENEMYROBOT( pSoldier ) )
{
return( fRecompileMovementCosts );
}
if ( pExplosive->ubType == EXPLOSV_CREATUREGAS )
{
if ( pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER )
{
// unaffected by own gas effects
return( fRecompileMovementCosts );
}
}
// no gas mask helps from creature attacks and fire
if ( pExplosive->ubType == EXPLOSV_CREATUREGAS || pExplosive->ubType == EXPLOSV_BURNABLEGAS)
{
if ( sSubsequent && pSoldier->flags.fHitByGasFlags & HIT_BY_CREATUREGAS )
{
// already affected by creature gas this turn
return( fRecompileMovementCosts );
}
// Who cares if he was affected already? Running through a gas cloud is not good for health so let him suffer!
if ( /*sSubsequent &&*/ pSoldier->flags.fHitByGasFlags & HIT_BY_BURNABLEGAS )
{
// Already affected by burnable gas this turn. Lower damage value by ini setting.
fGasDamageModifier = gItemSettings.fDamageHealthMoveModifierExplosive;
fGasBreathDamageModifier = gItemSettings.fDamageBreathMoveModifierExplosive;
// modify damage values
sWoundAmt = (INT16)(sWoundAmt * fGasDamageModifier);
sBreathAmt = (INT16)(sBreathAmt * fGasBreathDamageModifier);
//return( fRecompileMovementCosts );
}
}
else //
// ATE/CJC: gas stuff
{
if ( pExplosive->ubType == EXPLOSV_TEARGAS )
{
// zombies and robots are unaffected by tear gas
if ( AM_A_ROBOT( pSoldier ) || pSoldier->IsZombie() )
{
return( fRecompileMovementCosts );
}
// ignore whether subsequent or not if hit this turn
// Who cares if he was affected already? Running through a gas cloud is not good for health so let him suffer!
if ( pSoldier->flags.fHitByGasFlags & HIT_BY_TEARGAS )
{
// Already affected by tear gas this turn. Lower damage value by ini setting.
fGasDamageModifier = gItemSettings.fDamageHealthMoveModifierExplosive;
fGasBreathDamageModifier = gItemSettings.fDamageBreathMoveModifierExplosive;
//return( fRecompileMovementCosts );
}
}
else if ( pExplosive->ubType == EXPLOSV_MUSTGAS )
{
// zombies and robots are unaffected by mustard gas
if ( AM_A_ROBOT( pSoldier ) || pSoldier->IsZombie() )
{
return( fRecompileMovementCosts );
}
// Who cares if he was affected already? Running through a gas cloud is not good for health so let him suffer!
if ( /*sSubsequent &&*/ pSoldier->flags.fHitByGasFlags & HIT_BY_MUSTARDGAS )
{
// Already affected by mustard gas this turn. Lower damage value by ini setting.
fGasDamageModifier = gItemSettings.fDamageHealthMoveModifierExplosive;
fGasBreathDamageModifier = gItemSettings.fDamageBreathMoveModifierExplosive;
//return( fRecompileMovementCosts );
}
}
else if ( pExplosive->ubType == EXPLOSV_SMOKE || pExplosive->ubType == EXPLOSV_SMOKE_DEBRIS || pExplosive->ubType == EXPLOSV_SMOKE_FIRERETARDANT )//dnl ch40 200909
{
// robots are unaffected by smoke
if( AM_A_ROBOT(pSoldier) )
return(fRecompileMovementCosts);
// Who cares if he was affected already? Running through a gas cloud is not good for health so let him suffer!
if ( pSoldier->flags.fHitByGasFlags & HIT_BY_SMOKEGAS )
{
// Already affected by smoke this turn. Lower damage value by ini setting.
fGasDamageModifier = gItemSettings.fDamageHealthMoveModifierExplosive;
fGasBreathDamageModifier = gItemSettings.fDamageBreathMoveModifierExplosive;
//return( fRecompileMovementCosts );
}
}
// modify damage values
sWoundAmt = (INT16)(sWoundAmt * fGasDamageModifier);
sBreathAmt = (INT16)(sBreathAmt * fGasBreathDamageModifier);
bPosOfMask = FindGasMask(pSoldier);
if(!DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909
bPosOfMask = NO_SLOT;
if ( bPosOfMask == NO_SLOT || pSoldier->inv[ bPosOfMask ][0]->data.objectStatus < USABLE )
{
bPosOfMask = NO_SLOT;
}
//if ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK && pSoldier->inv[ HEAD1POS ][0]->data.objectStatus >= USABLE )
//{
// bPosOfMask = HEAD1POS;
//}
//else if ( pSoldier->inv[ HEAD2POS ].usItem == GASMASK && pSoldier->inv[ HEAD2POS ][0]->data.objectStatus >= USABLE )
//{
// bPosOfMask = HEAD2POS;
//}
if ( bPosOfMask != NO_SLOT )
{
if ( pSoldier->inv[ bPosOfMask ][0]->data.objectStatus < GASMASK_MIN_STATUS )
{
// GAS MASK reduces breath loss by its work% (it leaks if not at least 70%)
sBreathAmt = ( sBreathAmt * ( 100 - pSoldier->inv[ bPosOfMask ][0]->data.objectStatus ) ) / 100;
if ( sBreathAmt > 500 )
{
// if at least 500 of breath damage got through
// the soldier within the blast radius is gassed for at least one
// turn, possibly more if it's tear gas (which hangs around a while)
pSoldier->flags.uiStatusFlags |= SOLDIER_GASSED;
}
if ( pSoldier->flags.uiStatusFlags & SOLDIER_PC )
{
if ( sWoundAmt > 1 )
{
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus =
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus - (INT8) Random( 4 );
sWoundAmt = ( sWoundAmt * ( 100 - pSoldier->inv[ bPosOfMask ][0]->data.objectStatus ) ) / 100;
}
else if ( sWoundAmt == 1 )
{
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus =
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus - (INT8) Random( 2 );
}
}
}
else
{
sBreathAmt = 0;
if ( sWoundAmt > 0 )
{
if ( sWoundAmt == 1 )
{
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus =
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus - (INT8) Random( 2 );
}
else
{
// use up gas mask
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus =
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus - (INT8) Random( 4 );
}
}
sWoundAmt = 0;
}
}
}
if ( sWoundAmt != 0 || sBreathAmt != 0 )
{
switch( pExplosive->ubType )
{
case EXPLOSV_CREATUREGAS:
pSoldier->flags.fHitByGasFlags |= HIT_BY_CREATUREGAS;
break;
case EXPLOSV_TEARGAS:
pSoldier->flags.fHitByGasFlags |= HIT_BY_TEARGAS;
break;
case EXPLOSV_MUSTGAS:
pSoldier->flags.fHitByGasFlags |= HIT_BY_MUSTARDGAS;
break;
case EXPLOSV_BURNABLEGAS:
pSoldier->flags.fHitByGasFlags |= HIT_BY_BURNABLEGAS;
break;
case EXPLOSV_SMOKE://dnl ch40 200909
case EXPLOSV_SMOKE_DEBRIS:
case EXPLOSV_SMOKE_FIRERETARDANT:
pSoldier->flags.fHitByGasFlags |= HIT_BY_SMOKEGAS;
break;
default:
break;
}
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"ExpControl pSoldier->flags.fHitByGasFlags: %d", pSoldier->flags.fHitByGasFlags );
// Flugente: if we're frozen, we take no gas damage at all (because I say so)
if ( pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_CRYO] )
{
sWoundAmt = 0;
sBreathAmt = 0;
// fire makes us thaw faster
if ( pExplosive->ubType == EXPLOSV_BURNABLEGAS )
{
pSoldier->usSkillCooldown[SOLDIER_COOLDOWN_CRYO]--;
}
}
// Flugente: check for fire resistance
if ( pExplosive->ubType == EXPLOSV_BURNABLEGAS )
{
INT16 fireresistance = ArmourVersusFirePercent( pSoldier );
sWoundAmt = max( 0, sWoundAmt * ( 100 - fireresistance ) / 100 );
sBreathAmt = max( 0, sBreathAmt * ( 100 - fireresistance ) / 100 );
}
// a gas effect, take damage directly...
pSoldier->SoldierTakeDamage( ANIM_STAND, sWoundAmt, sBreathAmt, pExplosive->ubType == EXPLOSV_BURNABLEGAS ? TAKE_DAMAGE_GAS_FIRE : TAKE_DAMAGE_GAS_NOTFIRE, NOBODY, NOWHERE, 0, TRUE );
if (is_networked && is_client)
{
// if it gets here we are supposed to send it.
// let all the other clients know that our merc got gassed
// and align them with our random number generator
if (IsOurSoldier(pSoldier) || (pSoldier->bTeam == 1 && is_server) && !fFromRemoteClient)
{
send_gasdamage( pSoldier , pExplosive->uiIndex , sSubsequent , fRecompileMovementCosts , sWoundAmt , sBreathAmt , ubOwner );
}
}
if ( pSoldier->stats.bLife >= CONSCIOUSNESS )
{
pSoldier->DoMercBattleSound( BATTLE_SOUND_HIT1 );
}
if ( ubOwner != NOBODY && MercPtrs[ ubOwner ]->bTeam == gbPlayerNum && pSoldier->bTeam != gbPlayerNum )
{
ProcessImplicationsOfPCAttack( MercPtrs[ ubOwner ], &pSoldier, REASON_EXPLOSION );
}
}
return( fRecompileMovementCosts );
}
BOOLEAN ExpAffect( INT32 sBombGridNo, INT32 sGridNo, UINT32 uiDist, UINT16 usItem, UINT8 ubOwner, INT16 sSubsequent, BOOLEAN *pfMercHit, INT8 bLevel, INT32 iSmokeEffectID )
{
#ifdef JA2BETAVERSION
if (is_networked) {
CHAR tmpMPDbgString[512];
sprintf(tmpMPDbgString,"ExpAffect ( sBombGridNo : %i , sGridNo : %i , uiDist : %i , usItem : %i , ubOwner : %i , sSubsequent : %i , fMercHit : %i , bLevel : %i , iSmokeEffectID : %i )\n",sBombGridNo, sGridNo , uiDist , usItem , ubOwner , sSubsequent , (int)*pfMercHit , bLevel , iSmokeEffectID );
MPDebugMsg(tmpMPDbgString);
}
#endif
INT16 sWoundAmt = 0,sBreathAmt = 0, /* sNewWoundAmt = 0, sNewBreathAmt = 0, */ sStructDmgAmt;
UINT8 ubPerson;
SOLDIERTYPE *pSoldier;
EXPLOSIVETYPE *pExplosive;
INT16 sX, sY;
BOOLEAN fRecompileMovementCosts = FALSE;
BOOLEAN fSmokeEffect = FALSE;
BOOLEAN fStunEffect = FALSE;
BOOLEAN fBlastEffect = TRUE;
BOOLEAN fBloodEffect = FALSE;
INT8 bSmokeEffectType = 0;
INT32 sNewGridNo;
ITEM_POOL * pItemPool, * pItemPoolNext;
UINT32 uiRoll;
//Init the variables
sX = sY = -1;
// sevenfm: don't spread fire on water
if (Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_BURNABLEGAS &&
Water(sGridNo, bLevel))
{
return FALSE;
}
if ( sSubsequent == BLOOD_SPREAD_EFFECT )
{
fSmokeEffect = FALSE;
fBlastEffect = FALSE;
fBloodEffect = TRUE;
}
else
{
// Turn off blast effect if some types of items...
switch( Explosive[Item[usItem].ubClassIndex].ubType )
{
case EXPLOSV_MUSTGAS:
fSmokeEffect = TRUE;
bSmokeEffectType = MUSTARDGAS_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
case EXPLOSV_BURNABLEGAS:
fSmokeEffect = TRUE;
bSmokeEffectType = BURNABLEGAS_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
case EXPLOSV_TEARGAS:
fSmokeEffect = TRUE;
bSmokeEffectType = TEARGAS_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
case EXPLOSV_SMOKE:
fSmokeEffect = TRUE;
bSmokeEffectType = NORMAL_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
case EXPLOSV_STUN:
fStunEffect = TRUE;
break;
case EXPLOSV_CREATUREGAS:
fSmokeEffect = TRUE;
bSmokeEffectType = CREATURE_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
case EXPLOSV_SIGNAL_SMOKE:
fSmokeEffect = TRUE;
bSmokeEffectType = SIGNAL_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
case EXPLOSV_SMOKE_DEBRIS:
fSmokeEffect = TRUE;
bSmokeEffectType = DEBRIS_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
case EXPLOSV_SMOKE_FIRERETARDANT:
fSmokeEffect = TRUE;
bSmokeEffectType = FIRERETARDANT_SMOKE_EFFECT;
fBlastEffect = FALSE;
break;
}
}
// OK, here we:
// Get explosive data from table
pExplosive = &(Explosive[ Item[ usItem ].ubClassIndex ] );
uiRoll = PreRandom( 100 );
// Calculate wound amount
// HEADROCK HAM 3.6: Can now use negative modifier.
INT16 newDamage = (INT16)GetModifiedExplosiveDamage( pExplosive->ubDamage, 0 );
//INT16 newDamage = pExplosive->ubDamage + (INT16)(( pExplosive->ubDamage * gGameExternalOptions.ubExplosivesDamageMultiplier) / 100); //lal
//DBrot: apply a modifier to confined explosions
if(InARoom(sBombGridNo, NULL)){
newDamage += (INT16)(newDamage * pExplosive->bIndoorModifier);
}
sWoundAmt = newDamage + (INT16) ( (newDamage * uiRoll) / 100 );
// Calculate breath amount ( if stun damage applicable )
INT16 newBreath = (INT16)GetModifiedExplosiveDamage( pExplosive->ubStunDamage, 1 );
//INT16 newBreath = pExplosive->ubStunDamage + (INT16)(( pExplosive->ubStunDamage * gGameExternalOptions.ubExplosivesDamageMultiplier) / 100); //lal
if(InARoom(sBombGridNo, NULL)){
newBreath += (INT16)(newBreath * pExplosive->bIndoorModifier);
}
sBreathAmt = ( newBreath * 100 ) + (INT16) ( ( ( newBreath / 2 ) * 100 * uiRoll ) / 100 ) ;
// ATE: Make sure guys get pissed at us!
HandleBuldingDestruction( sGridNo ,ubOwner );
if ( fBlastEffect )
{
// lower effects for distance away from center of explosion
// If radius is 3, damage % is (100)/66/33/17
// If radius is 5, damage % is (100)/80/60/40/20/10
// If radius is 8, damage % is (100)/88/75/63/50/37/25/13/6
if ( pExplosive->ubRadius == 0 )
{
// leave as is, has to be at range 0 here
}
else if (uiDist < pExplosive->ubRadius)
{
// if radius is 5, go down by 5ths ~ 20%
sWoundAmt = sWoundAmt - (INT16) (sWoundAmt * uiDist / pExplosive->ubRadius );
sBreathAmt = sBreathAmt - (INT16) (sBreathAmt * uiDist / pExplosive->ubRadius );
}
else
{
// at the edge of the explosion, do half the previous damage
sWoundAmt = (INT16) ( (sWoundAmt / pExplosive->ubRadius) / 2);
sBreathAmt = (INT16) ( (sBreathAmt / pExplosive->ubRadius) / 2);
}
if (sWoundAmt < 0)
sWoundAmt = 0;
if (sBreathAmt < 0)
sBreathAmt = 0;
// damage structures
if ( uiDist <= __max( 1, (UINT32) (newDamage / 30) ) )
{
if ( Item[ usItem ].usItemClass & IC_GRENADE )
{
sStructDmgAmt = sWoundAmt / 3;
}
else // most explosives
{
sStructDmgAmt = sWoundAmt;
}
// Flugente: roof collapse
// Only do this at the source of the explosion (uiDist == 0).
if ( !uiDist )
{
// walls are attributed to a gridno. As a result, sometimes the outer wall of a house is located at a gridno that does not 'belong' to the house.
// This gridno will then have no roof over it, which HandleRoofDestruction(..) requires.
// For this reason, we call it also at neighbouring gridnos.
// If the explosion, however, is on a roof itself, no need for these shenanigans.
if ( bLevel )
{
HandleRoofDestruction( sGridNo, sWoundAmt );
}
else
{
HandleRoofDestruction( sGridNo, sWoundAmt * 0.75f );
HandleRoofDestruction( NewGridNo( sGridNo, DirectionInc( NORTH )), sWoundAmt * 0.75f );
HandleRoofDestruction( NewGridNo( sGridNo, DirectionInc( EAST )), sWoundAmt * 0.75f );
HandleRoofDestruction( NewGridNo( sGridNo, DirectionInc( WEST )), sWoundAmt * 0.75f );
HandleRoofDestruction( NewGridNo( sGridNo, DirectionInc( SOUTH )), sWoundAmt * 0.75f );
}
}
ExplosiveDamageGridNo( sGridNo, sStructDmgAmt, uiDist, &fRecompileMovementCosts, FALSE, -1, 0 , ubOwner, bLevel, STRUCTURE_DAMAGE_EXPLOSION );
// ATE: Look for damage to walls ONLY for next two gridnos
sNewGridNo = NewGridNo( sGridNo, DirectionInc( NORTH ) );
if ( GridNoOnVisibleWorldTile( sNewGridNo ) )
{
ExplosiveDamageGridNo( sNewGridNo, sStructDmgAmt, uiDist, &fRecompileMovementCosts, TRUE, -1, 0, ubOwner, bLevel, STRUCTURE_DAMAGE_EXPLOSION );
}
// ATE: Look for damage to walls ONLY for next two gridnos
sNewGridNo = NewGridNo( sGridNo, DirectionInc( WEST ) );
if ( GridNoOnVisibleWorldTile( sNewGridNo ) )
{
ExplosiveDamageGridNo( sNewGridNo, sStructDmgAmt, uiDist, &fRecompileMovementCosts, TRUE, -1, 0, ubOwner, bLevel, STRUCTURE_DAMAGE_EXPLOSION );
}
}
// Add burn marks to ground randomly....
INT8 bOverTerrainType = GetTerrainType( sGridNo );
if ( pExplosive->ubType == EXPLOSV_NORMAL &&
( bOverTerrainType == FLAT_GROUND || bOverTerrainType == DIRT_ROAD || bOverTerrainType == LOW_GRASS || bOverTerrainType == HIGH_GRASS || bOverTerrainType == PAVED_ROAD ) &&
uiDist == 0 &&
bLevel == 0 &&
!HasItemFlag( usItem, JUMP_GRENADE ) &&
sWoundAmt > (INT16)Random( 100 ) )
//Random( 50 ) < 15 &&
//sWoundAmt / (4 * uiDist + 1) > Random( 100 ) )
//pExplosive->ubDamage > Random( 100 ) )
{
UINT16 usObjectIndex;
UINT16 usTileIndex;
//if ( !TypeRangeExistsInObjectLayer( sGridNo, FIRSTEXPLDEBRIS, SECONDEXPLDEBRIS, &usObjectIndex ) )
if ( !TypeRangeExistsInObjectLayer( sGridNo, FIRSTEXPLDEBRIS, SECONDLARGEEXPDEBRIS10, &usObjectIndex ) )
{
//GetTileIndexFromTypeSubIndex( SECONDEXPLDEBRIS, (UINT16)( Random( 10 ) + 1 ), &usTileIndex );
GetTileIndexFromTypeSubIndex( SECONDEXPLDEBRIS, (UINT16)( 1 + 3 * Random( 2 ) ), &usTileIndex );
ApplyMapChangesToMapTempFile( TRUE );
AddObjectToHead( sGridNo, usTileIndex );
ApplyMapChangesToMapTempFile( FALSE );
SetRenderFlags( RENDER_FLAG_FULL );
}
}
// NB radius can be 0 so cannot divide it by 2 here
if (!fStunEffect && (uiDist * 2 <= pExplosive->ubRadius) )
{
GetItemPool( sGridNo, &pItemPool, bLevel );
while( pItemPool )
{
pItemPoolNext = pItemPool->pNext;
if ( DamageItemOnGround( &(gWorldItems[ pItemPool->iItemIndex ].object), sGridNo, bLevel, (INT32) (sWoundAmt * 2), ubOwner ) )
{
// item was destroyed
RemoveItemFromPool( sGridNo, pItemPool->iItemIndex, bLevel );
// sevenfm: if no other bomb exists here
CheckForBuriedBombsAndRemoveFlags( sGridNo, bLevel );
}
pItemPool = pItemPoolNext;
}
/*
// Search for an explosive item in item pool
while ( ( iWorldItem = GetItemOfClassTypeInPool( sGridNo, IC_EXPLOSV, bLevel ) ) != -1 )
{
// Get usItem
usItem = gWorldItems[ iWorldItem ].object.usItem;
DamageItem
if ( CheckForChainReaction( usItem, gWorldItems[ iWorldItem ].object[0]->data.objectStatus, sWoundAmt, TRUE ) )
{
RemoveItemFromPool( sGridNo, iWorldItem, bLevel );
// OK, Ignite this explosion!
IgniteExplosion( NOBODY, sX, sY, 0, sGridNo, usItem, bLevel );
}
else
{
RemoveItemFromPool( sGridNo, iWorldItem, bLevel );
}
}
// Remove any unburied items here!
RemoveAllUnburiedItems( sGridNo, bLevel );
*/
}
// Flugente: stun grenades remove smoke. Because... air pressure, or something
// the smoke comes back on the next update, though (the spread isn't dependent on adjacent tiles, so having it slowly return is not happening for now)
// still, this does allow us to remove gas effects for one turn
else if ( fStunEffect )
{
RemoveSmokeEffectFromTile( sGridNo, bLevel );
}
}
else if ( fSmokeEffect )
{
// Flugente: if adding fire retardant, remove fire
if ( pExplosive->ubType == EXPLOSV_SMOKE_FIRERETARDANT )
{
if ( gpWorldLevelData[sGridNo].ubExtFlags[bLevel] & MAPELEMENT_EXT_BURNABLEGAS )
{
RemoveSmokeEffectFromTile( sGridNo, bLevel );
}
}
// If tear gar, determine turns to spread.....
if ( sSubsequent == ERASE_SPREAD_EFFECT )
{
RemoveSmokeEffectFromTile( sGridNo, bLevel );
}
else if ( sSubsequent != REDO_SPREAD_EFFECT )
{
AddSmokeEffectToTile( iSmokeEffectID, bSmokeEffectType, sGridNo, bLevel );
}
}
else
{
// Drop blood ....
// Get blood quantity....
InternalDropBlood( sGridNo, 0, 0, (UINT8)(__max( ( MAXBLOODQUANTITY - ( uiDist * 2 ) ), 0 ) ), 1 );
}
if ( sSubsequent != ERASE_SPREAD_EFFECT && sSubsequent != BLOOD_SPREAD_EFFECT )
{
// if an explosion effect....
if ( fBlastEffect )
{
// don't hurt anyone who is already dead & waiting to be removed
if ( ( ubPerson = WhoIsThere2( sGridNo, bLevel ) ) != NOBODY )
{
DamageSoldierFromBlast( ubPerson, ubOwner, sBombGridNo, sWoundAmt, sBreathAmt, uiDist, usItem, sSubsequent );
}
if ( bLevel == 1 )
{
if ( ( ubPerson = WhoIsThere2(sGridNo, 0 ) ) != NOBODY )
{
if ( (sWoundAmt / 2) > 20 )
{
// debris damage!
if ( (sBreathAmt / 2) > 20 )
{
DamageSoldierFromBlast( ubPerson, ubOwner, sBombGridNo, (INT16) Random( (sWoundAmt / 2) - 20 ), (INT16) Random( (sBreathAmt / 2) - 20 ), uiDist, usItem, sSubsequent );
}
else
{
DamageSoldierFromBlast( ubPerson, ubOwner, sBombGridNo, (INT16) Random( (sWoundAmt / 2) - 20 ), 1, uiDist, usItem, sSubsequent );
}
}
}
}
}
else
{
if ( ( ubPerson = WhoIsThere2(sGridNo, bLevel ) ) >= TOTAL_SOLDIERS )
{
return( fRecompileMovementCosts );
}
pSoldier = MercPtrs[ ubPerson ]; // someone is here, and they're gonna get hurt
// silversurfer: Gas now only has an effect when the container had time to emit some. Initially it will do nothing.
// This prevents the problem that we have to suffer two times without a chance to react (1st when the grenade hits our position, 2nd when our turn starts)
// Flugente: only deal damage when smoke exists
if ( sSubsequent > 0 && (gpWorldLevelData[sGridNo].ubExtFlags[bLevel] & ANY_SMOKE_EFFECT) )
fRecompileMovementCosts = DishOutGasDamage( pSoldier, pExplosive, sSubsequent, fRecompileMovementCosts, sWoundAmt, sBreathAmt, ubOwner );
/*
if (!pSoldier->bActive || !pSoldier->bInSector || !pSoldier->stats.bLife || AM_A_ROBOT( pSoldier ) )
{
return( fRecompileMovementCosts );
}
if ( pExplosive->ubType == EXPLOSV_CREATUREGAS )
{
if ( pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER )
{
// unaffected by own gas effects
return( fRecompileMovementCosts );
}
if ( sSubsequent && pSoldier->flags.fHitByGasFlags & HIT_BY_CREATUREGAS )
{
// already affected by creature gas this turn
return( fRecompileMovementCosts );
}
}
else // no gas mask help from creature attacks
// ATE/CJC: gas stuff
{
INT8 bPosOfMask = NO_SLOT;
if ( pExplosive->ubType == EXPLOSV_TEARGAS )
{
// ignore whether subsequent or not if hit this turn
if ( pSoldier->flags.fHitByGasFlags & HIT_BY_TEARGAS )
{
// already affected by creature gas this turn
return( fRecompileMovementCosts );
}
}
else if ( pExplosive->ubType == EXPLOSV_MUSTGAS )
{
if ( sSubsequent && pSoldier->flags.fHitByGasFlags & HIT_BY_MUSTARDGAS )
{
// already affected by creature gas this turn
return( fRecompileMovementCosts );
}
}
if ( sSubsequent && pSoldier->flags.fHitByGasFlags & HIT_BY_CREATUREGAS )
{
// already affected by creature gas this turn
return( fRecompileMovementCosts );
}
if ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK && pSoldier->inv[ HEAD1POS ][0]->data.objectStatus >= USABLE )
{
bPosOfMask = HEAD1POS;
}
else if ( pSoldier->inv[ HEAD2POS ].usItem == GASMASK && pSoldier->inv[ HEAD2POS ][0]->data.objectStatus >= USABLE )
{
bPosOfMask = HEAD2POS;
}
if ( bPosOfMask != NO_SLOT )
{
if ( pSoldier->inv[ bPosOfMask ][0]->data.objectStatus < GASMASK_MIN_STATUS )
{
// GAS MASK reduces breath loss by its work% (it leaks if not at least 70%)
sBreathAmt = ( sBreathAmt * ( 100 - pSoldier->inv[ bPosOfMask ][0]->data.objectStatus ) ) / 100;
if ( sBreathAmt > 500 )
{
// if at least 500 of breath damage got through
// the soldier within the blast radius is gassed for at least one
// turn, possibly more if it's tear gas (which hangs around a while)
pSoldier->flags.uiStatusFlags |= SOLDIER_GASSED;
}
if ( sWoundAmt > 1 )
{
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus -= (INT8) Random( 4 );
sWoundAmt = ( sWoundAmt * ( 100 - pSoldier->inv[ bPosOfMask ][0]->data.objectStatus ) ) / 100;
}
else if ( sWoundAmt == 1 )
{
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus -= (INT8) Random( 2 );
}
}
else
{
sBreathAmt = 0;
if ( sWoundAmt > 0 )
{
if ( sWoundAmt == 1 )
{
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus -= (INT8) Random( 2 );
}
else
{
// use up gas mask
pSoldier->inv[ bPosOfMask ][0]->data.objectStatus -= (INT8) Random( 4 );
}
}
sWoundAmt = 0;
}
}
}
if ( sWoundAmt != 0 || sBreathAmt != 0 )
{
switch( pExplosive->ubType )
{
case EXPLOSV_CREATUREGAS:
pSoldier->flags.fHitByGasFlags |= HIT_BY_CREATUREGAS;
break;
case EXPLOSV_TEARGAS:
pSoldier->flags.fHitByGasFlags |= HIT_BY_TEARGAS;
break;
case EXPLOSV_MUSTGAS:
pSoldier->flags.fHitByGasFlags |= HIT_BY_MUSTARDGAS;
break;
default:
break;
}
// a gas effect, take damage directly...
pSoldier->SoldierTakeDamage( ANIM_STAND, sWoundAmt, sBreathAmt, TAKE_DAMAGE_GAS, NOBODY, NOWHERE, 0, TRUE );
if ( pSoldier->stats.bLife >= CONSCIOUSNESS )
{
pSoldier->DoMercBattleSound( (INT8)( BATTLE_SOUND_HIT1 + Random( 2 ) ) );
}
}
*/
}
(*pfMercHit) = TRUE;
}
return( fRecompileMovementCosts );
}
void GetRayStopInfo( UINT32 uiNewSpot, UINT8 ubDir, INT8 bLevel, BOOLEAN fSmokeEffect, INT32 uiCurRange, INT32 *piMaxRange, UINT8 *pubKeepGoing )
{
INT8 bStructHeight;
UINT8 ubMovementCost;
INT8 Blocking, BlockingTemp;
BOOLEAN fTravelCostObs = FALSE;
UINT32 uiRangeReduce;
INT32 sNewGridNo;
STRUCTURE * pBlockingStructure = NULL;
BOOLEAN fBlowWindowSouth = FALSE;
BOOLEAN fReduceRay = TRUE;
ubMovementCost = gubWorldMovementCosts[ uiNewSpot ][ ubDir ][ bLevel ];
if ( IS_TRAVELCOST_DOOR( ubMovementCost ) )
{
ubMovementCost = DoorTravelCost( NULL, uiNewSpot, ubMovementCost, FALSE, NULL );
// If we have hit a wall, STOP HERE
if (ubMovementCost >= TRAVELCOST_BLOCKED)
{
fTravelCostObs = TRUE;
}
}
else
{
// If we have hit a wall, STOP HERE
if ( ubMovementCost == TRAVELCOST_WALL )
{
// We have an obstacle here..
fTravelCostObs = TRUE;
}
}
Blocking = GetBlockingStructureInfo( uiNewSpot, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );//dnl ch53 111009
if ( pBlockingStructure )
{
if ( pBlockingStructure->fFlags & STRUCTURE_CAVEWALL )
{
// block completely!
fTravelCostObs = TRUE;
}
else if ( pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
{
// not stopped
fTravelCostObs = FALSE;
fReduceRay = FALSE;
}
}
// ATE: For smoke, don't let it go over roof....
// 0verhaul: Why not?
if ( fSmokeEffect )
{
if ( bLevel )
{
STRUCTURE * pStructure;
// Check for roof here....
pStructure = FindStructure( uiNewSpot, STRUCTURE_ROOF );
if ( pStructure == NULL )
{
// block completely!
fTravelCostObs = TRUE;
}
}
}
if ( fTravelCostObs )
{
if ( fSmokeEffect )
{
if ( Blocking == BLOCKING_TOPRIGHT_OPEN_WINDOW || Blocking == BLOCKING_TOPLEFT_OPEN_WINDOW )
{
// If open, fTravelCostObs set to false and reduce range....
fTravelCostObs = FALSE;
// Range will be reduced below...
}
if ( fTravelCostObs )
{
// ATE: For windows, check to the west and north for a broken window, as movement costs
// will override there...
sNewGridNo = NewGridNo( uiNewSpot, DirectionInc( WEST ) );
BlockingTemp = GetBlockingStructureInfo( sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );
if ( BlockingTemp == BLOCKING_TOPRIGHT_OPEN_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_OPEN_WINDOW )
{
// If open, fTravelCostObs set to false and reduce range....
fTravelCostObs = FALSE;
// Range will be reduced below...
}
if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
{
fTravelCostObs = FALSE;
fReduceRay = FALSE;
}
}
if ( fTravelCostObs )
{
sNewGridNo = NewGridNo( uiNewSpot, DirectionInc( NORTH ) );
BlockingTemp = GetBlockingStructureInfo( sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );
if ( BlockingTemp == BLOCKING_TOPRIGHT_OPEN_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_OPEN_WINDOW )
{
// If open, fTravelCostObs set to false and reduce range....
fTravelCostObs = FALSE;
// Range will be reduced below...
}
if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
{
fTravelCostObs = FALSE;
fReduceRay = FALSE;
}
}
}
else
{
// We are a blast effect....
// ATE: explode windows!!!!
if ( Blocking == BLOCKING_TOPLEFT_WINDOW || Blocking == BLOCKING_TOPRIGHT_WINDOW )
{
// Explode!
if ( ubDir == SOUTH || ubDir == SOUTHEAST || ubDir == SOUTHWEST )
{
fBlowWindowSouth = TRUE;
}
if ( pBlockingStructure != NULL )
{
WindowHit( uiNewSpot, pBlockingStructure->usStructureID, fBlowWindowSouth, TRUE );
}
}
// ATE: For windows, check to the west and north for a broken window, as movement costs
// will override there...
sNewGridNo = NewGridNo( uiNewSpot, DirectionInc( WEST ) );
BlockingTemp = GetBlockingStructureInfo( sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure , TRUE );
if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
{
fTravelCostObs = FALSE;
fReduceRay = FALSE;
}
if ( BlockingTemp == BLOCKING_TOPRIGHT_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_WINDOW )
{
if ( pBlockingStructure != NULL )
{
WindowHit( sNewGridNo, pBlockingStructure->usStructureID, FALSE, TRUE );
}
}
sNewGridNo = NewGridNo( uiNewSpot, DirectionInc( NORTH ) );
BlockingTemp = GetBlockingStructureInfo( sNewGridNo, ubDir, 0, bLevel, &bStructHeight, &pBlockingStructure, TRUE );
if ( pBlockingStructure && pBlockingStructure->pDBStructureRef->pDBStructure->ubDensity <= 15 )
{
fTravelCostObs = FALSE;
fReduceRay = FALSE;
}
if ( BlockingTemp == BLOCKING_TOPRIGHT_WINDOW || BlockingTemp == BLOCKING_TOPLEFT_WINDOW )
{
if ( pBlockingStructure != NULL )
{
WindowHit( sNewGridNo, pBlockingStructure->usStructureID, FALSE, TRUE );
}
}
}
}
// Have we hit things like furniture, etc?
if ( Blocking != NOTHING_BLOCKING && !fTravelCostObs )
{
// ATE: Tall things should blaock all
if ( bStructHeight == 4 )
{
(*pubKeepGoing) = FALSE;
}
else
{
// If we are smoke, reduce range variably....
if ( fReduceRay )
{
if ( fSmokeEffect )
{
switch( bStructHeight )
{
case 3:
uiRangeReduce = 2;
break;
case 2:
uiRangeReduce = 1;
break;
default:
uiRangeReduce = 0;
break;
}
}
else
{
uiRangeReduce = 2;
}
( *piMaxRange ) -= uiRangeReduce;
}
if ( uiCurRange <= (*piMaxRange) )
{
(*pubKeepGoing) = TRUE;
}
else
{
(*pubKeepGoing) = FALSE;
}
}
}
else
{
if ( fTravelCostObs )
{
( *pubKeepGoing ) = FALSE;
}
else
{
( *pubKeepGoing ) = TRUE;
}
}
}
void SpreadEffect( INT32 sGridNo, UINT8 ubRadius, UINT16 usItem, UINT8 ubOwner, BOOLEAN fSubsequent, INT8 bLevel, INT32 iSmokeEffectID , BOOL fFromRemoteClient , BOOL fNewSmokeEffect )
{
if (is_networked && is_client)
{
SOLDIERTYPE* pAttacker = MercPtrs[ubOwner];
if (pAttacker != NULL)
{
if (IsOurSoldier(pAttacker) || (pAttacker->bTeam == 1 && is_server))
{
// dont send SpreadEffect if it was just called from NewSmokeEffect - as now we sync that seperately
if (!fNewSmokeEffect)
{
// let all the other clients know we are spawning this effect
// and align them with our random number generator
send_spreadeffect(sGridNo,ubRadius,usItem,ubOwner,fSubsequent,bLevel,iSmokeEffectID);
}
}
else if (!fFromRemoteClient)
{
// skip executing locally because we want the random number generator to be aligned
// with the client that spawns set off the explosion/grenade/whatever
return;
}
// Flugente: campaign stats
if ( Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_MUSTGAS )
{
if ( IsOurSoldier(pAttacker) )
gCurrentIncident.usIncidentFlags |= INCIDENT_MUSTARDGAS_PLAYERSIDE;
else
gCurrentIncident.usIncidentFlags |= INCIDENT_MUSTARDGAS_ENEMY;
}
}
#ifdef JA2BETAVERSION
CHAR tmpMPDbgString[512];
sprintf(tmpMPDbgString,"SpreadEffect ( sGridNo : %i , ubRadius : %i , usItem : %i , ubOwner : %i , fSubsequent : %i , bLevel : %i , iSmokeEffectID : %i , fFromRemote : %i , fNewSmoke : %i )\n",sGridNo, ubRadius , usItem , ubOwner , (int)fSubsequent , bLevel , iSmokeEffectID , fFromRemoteClient , fNewSmokeEffect );
MPDebugMsg(tmpMPDbgString);
gfMPDebugOutputRandoms = true;
#endif
}
// Flugente: if tile has a fire retardant effect, don't create new fire
if ( Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_BURNABLEGAS )
{
if ( gpWorldLevelData[sGridNo].ubExtFlags[bLevel] & MAPELEMENT_EXT_FIRERETARDANT_SMOKE )
{
return;
}
}
INT32 uiNewSpot, uiTempSpot, uiBranchSpot, cnt, branchCnt;
INT32 uiTempRange, ubBranchRange;
UINT8 ubDir,ubBranchDir, ubKeepGoing;
INT16 sRange;
BOOLEAN fRecompileMovement = FALSE;
BOOLEAN fAnyMercHit = FALSE;
BOOLEAN fSmokeEffect = FALSE;
switch( Explosive[Item[usItem].ubClassIndex].ubType )
{
case EXPLOSV_MUSTGAS:
case EXPLOSV_BURNABLEGAS:
case EXPLOSV_TEARGAS:
case EXPLOSV_SMOKE:
case EXPLOSV_CREATUREGAS:
case EXPLOSV_SIGNAL_SMOKE:
case EXPLOSV_SMOKE_DEBRIS:
case EXPLOSV_SMOKE_FIRERETARDANT:
fSmokeEffect = TRUE;
break;
}
// sevenfm: create light effect for fire and signal smoke
if (gGameExternalOptions.bAddLightAfterExplosion &&
fSubsequent != ERASE_SPREAD_EFFECT &&
((gubEnvLightValue >= NORMAL_LIGHTLEVEL_NIGHT - 3) || gbWorldSectorZ) &&
(Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_BURNABLEGAS || Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_SIGNAL_SMOKE) &&
bLevel == 0 &&
!Water(sGridNo, bLevel))
{
NewLightEffect(sGridNo, 1, ubRadius);
}
/*if(is_networked)
{
ScreenMsg( FONT_LTBLUE, MSG_MPSYSTEM, L"explosives not coded in MP");
return;
}*/
// Set values for recompile region to optimize area we need to recompile for MPs
gsRecompileAreaTop = sGridNo / WORLD_COLS;
gsRecompileAreaLeft = sGridNo % WORLD_COLS;
gsRecompileAreaRight = gsRecompileAreaLeft;
gsRecompileAreaBottom = gsRecompileAreaTop;
// multiply range by 2 so we can correctly calculate approximately round explosion regions
sRange = ubRadius * 2;
// first, affect main spot
if ( ExpAffect( sGridNo, sGridNo, 0, usItem, ubOwner, fSubsequent, &fAnyMercHit, bLevel, iSmokeEffectID ) )
{
fRecompileMovement = TRUE;
}
for (ubDir = NORTH; ubDir <= NORTHWEST; ubDir++ )
{
uiTempSpot = sGridNo;
uiTempRange = sRange;
if (ubDir & 1)
{
cnt = 3;
}
else
{
cnt = 2;
}
while( cnt <= uiTempRange) // end of range loop
{
// move one tile in direction
uiNewSpot = NewGridNo( uiTempSpot, DirectionInc( ubDir ) );
// see if this was a different spot & if we should be able to reach
// this spot
if (uiNewSpot == uiTempSpot)
{
ubKeepGoing = FALSE;
}
else
{
// Check if struct is a tree, etc and reduce range...
GetRayStopInfo( uiNewSpot, ubDir, bLevel, fSmokeEffect, cnt, &uiTempRange, &ubKeepGoing );
}
if (ubKeepGoing)
{
uiTempSpot = uiNewSpot;
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Explosion affects %d", uiNewSpot) );
// ok, do what we do here...
if ( ExpAffect( sGridNo, uiNewSpot, cnt / 2, usItem, ubOwner, fSubsequent, &fAnyMercHit, bLevel, iSmokeEffectID ) )
{
fRecompileMovement = TRUE;
}
// how far should we branch out here?
ubBranchRange = (UINT8)( sRange - cnt );
if ( ubBranchRange )
{
// ok, there's a branch here. Mark where we start this branch.
uiBranchSpot = uiNewSpot;
// figure the branch direction - which is one dir clockwise
ubBranchDir = (ubDir + 1) % NUM_WORLD_DIRECTIONS;
if (ubBranchDir & 1)
{
branchCnt = 3;
}
else
{
branchCnt = 2;
}
while( branchCnt <= ubBranchRange) // end of range loop
{
ubKeepGoing = TRUE;
uiNewSpot = NewGridNo( uiBranchSpot, DirectionInc(ubBranchDir));
if (uiNewSpot != uiBranchSpot)
{
// Check if struct is a tree, etc and reduce range...
GetRayStopInfo( uiNewSpot, ubBranchDir, bLevel, fSmokeEffect, branchCnt, &ubBranchRange, &ubKeepGoing );
if ( ubKeepGoing )
{
// ok, do what we do here
//DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Explosion affects %d", uiNewSpot) );
if ( ExpAffect( sGridNo, uiNewSpot, (INT16)((cnt + branchCnt) / 2), usItem, ubOwner, fSubsequent, &fAnyMercHit, bLevel, iSmokeEffectID ) )
{
fRecompileMovement = TRUE;
}
uiBranchSpot = uiNewSpot;
}
//else
{
// check if it's ANY door, and if so, affect that spot so it's damaged
// if (RealDoorAt(uiNewSpot))
// {
// ExpAffect(sGridNo,uiNewSpot,cnt,ubReason,fSubsequent);
// }
// blocked, break out of the the sub-branch loop
// break;
}
}
if (ubBranchDir & 1)
{
branchCnt += 3;
}
else
{
branchCnt += 2;
}
}
} // end of if a branch to do
}
else // at edge, or tile blocks further spread in that direction
{
break;
}
if (ubDir & 1)
{
cnt += 3;
}
else
{
cnt += 2;
}
}
} // end of dir loop
// Recompile movement costs...
if ( fRecompileMovement )
{
INT16 sX, sY;
// DO wireframes as well
ConvertGridNoToXY( sGridNo, &sX, &sY );
SetRecalculateWireFrameFlagRadius( sX, sY, ubRadius );
CalculateWorldWireFrameTiles( FALSE );
RecompileLocalMovementCostsInAreaWithFlags();
RecompileLocalMovementCostsFromRadius( sGridNo, MAX_DISTANCE_EXPLOSIVE_CAN_DESTROY_STRUCTURES );
// if anything has been done to change movement costs and this is a potential POW situation, check
// paths for POWs
if (((gWorldSectorX == gModSettings.ubInitialPOWSectorX && gWorldSectorY == gModSettings.ubInitialPOWSectorY) ||
(gWorldSectorX == gModSettings.ubTixaPrisonSectorX && gWorldSectorY == gModSettings.ubTixaPrisonSectorY)) && gbWorldSectorZ == 0)
{
DoPOWPathChecks();
}
}
// do sight checks if something damaged or smoke stuff involved
if ( fRecompileMovement || fSmokeEffect )
{
if ( gubElementsOnExplosionQueue )
{
gfExplosionQueueMayHaveChangedSight = TRUE;
}
}
gsRecompileAreaTop = 0;
gsRecompileAreaLeft = 0;
gsRecompileAreaRight = 0;
gsRecompileAreaBottom = 0;
if (fAnyMercHit)
{
// reset explosion hit flag so we can damage mercs again
for ( cnt = 0; cnt < (INT32)guiNumMercSlots; cnt++ )
{
if ( MercSlots[ cnt ] )
{
MercSlots[ cnt ]->ubMiscSoldierFlags &= ~SOLDIER_MISC_HURT_BY_EXPLOSION;
}
}
}
if ( fSubsequent != BLOOD_SPREAD_EFFECT )
{
MakeNoise( NOBODY, sGridNo, bLevel, gpWorldLevelData[sGridNo].ubTerrainID, (UINT8)Explosive[ Item [ usItem ].ubClassIndex ].ubVolume, NOISE_EXPLOSION );
}
gfMPDebugOutputRandoms = false;
}
void ToggleActionItemsByFrequency( INT8 bFrequency )
{
UINT32 uiWorldBombIndex;
OBJECTTYPE * pObj;
// Go through all the bombs in the world, and look for remote ones
for (uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; uiWorldBombIndex++)
{
if (gWorldBombs[uiWorldBombIndex].fExists)
{
pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_REMOTE )
{
// Found a remote bomb, so check to see if it has the same frequency
if ((*pObj)[0]->data.misc.bFrequency == bFrequency)
{
// toggle its active flag
if ((*pObj).fFlags & OBJECT_DISABLED_BOMB)
{
(*pObj).fFlags &= (~OBJECT_DISABLED_BOMB);
}
else
{
(*pObj).fFlags |= OBJECT_DISABLED_BOMB;
}
}
}
}
}
}
void TogglePressureActionItemsInGridNo( INT32 sGridNo )
{
UINT32 uiWorldBombIndex;
OBJECTTYPE * pObj;
// Go through all the bombs in the world, and look for remote ones
for (uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; uiWorldBombIndex++)
{
if ( gWorldBombs[uiWorldBombIndex].fExists && gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].sGridNo == sGridNo )
{
pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_PRESSURE )
{
// Found a pressure item
// toggle its active flag
if ((*pObj).fFlags & OBJECT_DISABLED_BOMB)
{
(*pObj).fFlags &= (~OBJECT_DISABLED_BOMB);
}
else
{
(*pObj).fFlags |= OBJECT_DISABLED_BOMB;
}
}
}
}
}
void DelayedBillyTriggerToBlockOnExit( void )
{
if ( WhoIsThere2( gsTempActionGridNo, 0 ) == NOBODY )
{
TriggerNPCRecord( BILLY, 6 );
}
else
{
// delay further!
SetCustomizableTimerCallbackAndDelay( 1000, DelayedBillyTriggerToBlockOnExit, TRUE );
}
}
void BillyBlocksDoorCallback( void )
{
TriggerNPCRecord( BILLY, 6 );
}
BOOLEAN HookerInRoom( UINT16 usRoom )
{
//DBrot: More Rooms
UINT8 ubLoop;//, ubTempRoom;
UINT16 usTempRoom;
SOLDIERTYPE * pSoldier;
for ( ubLoop = gTacticalStatus.Team[ CIV_TEAM ].bFirstID; ubLoop <= gTacticalStatus.Team[ CIV_TEAM ].bLastID; ubLoop++ )
{
pSoldier = MercPtrs[ ubLoop ];
if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife >= OKLIFE && pSoldier->aiData.bNeutral && pSoldier->ubBodyType == MINICIV )
{
if ( InARoom( pSoldier->sGridNo, &usTempRoom ) && usTempRoom == usRoom )
{
return( TRUE );
}
}
}
return( FALSE );
}
void PerformItemAction( INT32 sGridNo, OBJECTTYPE * pObj )
{
#ifdef LUA_EXPLOSION_CONTROL
for (UINT32 i = ACTION_ITEM_OPEN_DOOR; i < NUM_ACTIONITEMS; /*= ACTION_ITEM_NEW;*/ i++ )
{
if ( (*pObj)[0]->data.misc.bActionValue == i )
LetLuaPerformItemAction( i, sGridNo, 0 );
}
#else
STRUCTURE * pStructure;
switch( (*pObj)[0]->data.misc.bActionValue )
{
case ACTION_ITEM_OPEN_DOOR:
pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
if (pStructure)
{
if (pStructure->fFlags & STRUCTURE_OPEN)
{
// it's already open - this MIGHT be an error but probably not
// because we are basically just ensuring that the door is open
}
else
{
if (pStructure->fFlags & STRUCTURE_BASE_TILE)
{
HandleDoorChangeFromGridNo( NULL, sGridNo, FALSE );
}
else
{
HandleDoorChangeFromGridNo( NULL, pStructure->sBaseGridNo, FALSE );
}
gfExplosionQueueMayHaveChangedSight = TRUE;
}
}
else
{
// error message here
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Action item to open door in gridno %d but there is none!", sGridNo );
#endif
}
break;
case ACTION_ITEM_CLOSE_DOOR:
pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
if (pStructure)
{
if (pStructure->fFlags & STRUCTURE_OPEN)
{
if (pStructure->fFlags & STRUCTURE_BASE_TILE)
{
HandleDoorChangeFromGridNo( NULL, sGridNo , FALSE );
}
else
{
HandleDoorChangeFromGridNo( NULL, pStructure->sBaseGridNo, FALSE );
}
gfExplosionQueueMayHaveChangedSight = TRUE;
}
else
{
// it's already closed - this MIGHT be an error but probably not
// because we are basically just ensuring that the door is closed
}
}
else
{
// error message here
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Action item to close door in gridno %d but there is none!", sGridNo );
#endif
}
break;
case ACTION_ITEM_TOGGLE_DOOR:
pStructure = FindStructure( sGridNo, STRUCTURE_ANYDOOR );
if (pStructure)
{
if (pStructure->fFlags & STRUCTURE_BASE_TILE)
{
HandleDoorChangeFromGridNo( NULL, sGridNo, FALSE );
}
else
{
HandleDoorChangeFromGridNo( NULL, pStructure->sBaseGridNo , FALSE );
}
gfExplosionQueueMayHaveChangedSight = TRUE;
}
else
{
// error message here
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Action item to toggle door in gridno %d but there is none!", sGridNo );
#endif
}
break;
case ACTION_ITEM_UNLOCK_DOOR:
{
DOOR * pDoor;
pDoor = FindDoorInfoAtGridNo( sGridNo );
if ( pDoor )
{
pDoor->fLocked = FALSE;
}
}
break;
case ACTION_ITEM_TOGGLE_LOCK:
{
DOOR * pDoor;
pDoor = FindDoorInfoAtGridNo( sGridNo );
if ( pDoor )
{
if ( pDoor->fLocked )
{
pDoor->fLocked = FALSE;
}
else
{
pDoor->fLocked = TRUE;
}
}
}
break;
case ACTION_ITEM_UNTRAP_DOOR:
{
DOOR * pDoor;
pDoor = FindDoorInfoAtGridNo( sGridNo );
if ( pDoor )
{
pDoor->ubTrapLevel = 0;
pDoor->ubTrapID = NO_TRAP;
}
}
break;
case ACTION_ITEM_SMALL_PIT:
Add3X3Pit( sGridNo );
SearchForOtherMembersWithinPitRadiusAndMakeThemFall( sGridNo, 1 );
break;
case ACTION_ITEM_LARGE_PIT:
Add5X5Pit( sGridNo );
SearchForOtherMembersWithinPitRadiusAndMakeThemFall( sGridNo, 2 );
break;
case ACTION_ITEM_TOGGLE_ACTION1:
ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 1 );
break;
case ACTION_ITEM_TOGGLE_ACTION2:
ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 2 );
break;
case ACTION_ITEM_TOGGLE_ACTION3:
ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 3 );
break;
case ACTION_ITEM_TOGGLE_ACTION4:
ToggleActionItemsByFrequency( FIRST_MAP_PLACED_FREQUENCY + 4 );
break;
case ACTION_ITEM_TOGGLE_PRESSURE_ITEMS:
TogglePressureActionItemsInGridNo( sGridNo );
break;
case ACTION_ITEM_ENTER_BROTHEL:
// JA2Gold: Disable brothel tracking
/*
if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
{
UINT8 ubID;
ubID = WhoIsThere2( sGridNo, 0 );
if ( (ubID != NOBODY) && (MercPtrs[ ubID ]->bTeam == gbPlayerNum) )
{
if ( MercPtrs[ ubID ]->sOldGridNo == sGridNo + DirectionInc( SOUTH ) )
{
gMercProfiles[ MADAME ].bNPCData2++;
SetFactTrue( FACT_PLAYER_USED_BROTHEL );
SetFactTrue( FACT_PLAYER_PASSED_GOON );
// If we for any reason trigger Madame's record 34 then we don't bother to do
// anything else
// Billy always moves back on a timer so that the player has a chance to sneak
// someone else through
// Madame's quote about female mercs should therefore not be made on a timer
if ( gMercProfiles[ MADAME ].bNPCData2 > 2 )
{
// more than 2 entering brothel
TriggerNPCRecord( MADAME, 35 );
return;
}
if ( gMercProfiles[ MADAME ].bNPCData2 == gMercProfiles[ MADAME ].bNPCData )
{
// full # of mercs who paid have entered brothel
// have Billy block the way again
SetCustomizableTimerCallbackAndDelay( 2000, BillyBlocksDoorCallback, FALSE );
//TriggerNPCRecord( BILLY, 6 );
}
else if ( gMercProfiles[ MADAME ].bNPCData2 > gMercProfiles[ MADAME ].bNPCData )
{
// more than full # of mercs who paid have entered brothel
// have Billy block the way again?
if ( CheckFact( FACT_PLAYER_FORCED_WAY_INTO_BROTHEL, 0 ) )
{
// player already did this once!
TriggerNPCRecord( MADAME, 35 );
return;
}
else
{
SetCustomizableTimerCallbackAndDelay( 2000, BillyBlocksDoorCallback, FALSE );
SetFactTrue( FACT_PLAYER_FORCED_WAY_INTO_BROTHEL );
TriggerNPCRecord( MADAME, 34 );
}
}
if ( gMercProfiles[ MercPtrs[ ubID ]->ubProfile ].bSex == FEMALE )
{
// woman walking into brothel
TriggerNPCRecordImmediately( MADAME, 33 );
}
}
else
{
// someone wants to leave the brothel
TriggerNPCRecord( BILLY, 5 );
}
}
}
*/
break;
case ACTION_ITEM_EXIT_BROTHEL:
// JA2Gold: Disable brothel tracking
/*
if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
{
UINT8 ubID;
ubID = WhoIsThere2( sGridNo, 0 );
if ( (ubID != NOBODY) && (MercPtrs[ ubID ]->bTeam == gbPlayerNum) && MercPtrs[ ubID ]->sOldGridNo == sGridNo + DirectionInc( NORTH ) )
{
gMercProfiles[ MADAME ].bNPCData2--;
if ( gMercProfiles[ MADAME ].bNPCData2 == 0 )
{
// reset paid #
gMercProfiles[ MADAME ].bNPCData = 0;
}
// Billy should move back to block the door again
gsTempActionGridNo = sGridNo;
SetCustomizableTimerCallbackAndDelay( 1000, DelayedBillyTriggerToBlockOnExit, TRUE );
}
}
*/
break;
case ACTION_ITEM_KINGPIN_ALARM:
PlayJA2Sample( KLAXON_ALARM, RATE_11025, SoundVolume( MIDVOLUME, sGridNo ), 5, SoundDir( sGridNo ) );
CallAvailableKingpinMenTo( sGridNo );
gTacticalStatus.fCivGroupHostile[ KINGPIN_CIV_GROUP ] = CIV_GROUP_HOSTILE;
{
UINT8 ubID, ubID2;
BOOLEAN fEnterCombat = FALSE;
for ( ubID = gTacticalStatus.Team[ CIV_TEAM ].bFirstID; ubID <= gTacticalStatus.Team[ CIV_TEAM ].bLastID; ubID++ )
{
if ( MercPtrs[ ubID ]->bActive && MercPtrs[ ubID ]->bInSector && MercPtrs[ ubID ]->ubCivilianGroup == KINGPIN_CIV_GROUP )
{
for ( ubID2 = gTacticalStatus.Team[ gbPlayerNum ].bFirstID; ubID2 <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubID2++ )
{
if ( MercPtrs[ ubID ]->aiData.bOppList[ ubID2 ] == SEEN_CURRENTLY )
{
MakeCivHostile( MercPtrs[ ubID ], 2 );
fEnterCombat = TRUE;
}
}
}
}
if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
{
EnterCombatMode( CIV_TEAM );
}
}
// now zap this object so it won't activate again
(*pObj).fFlags &= (~OBJECT_DISABLED_BOMB);
break;
case ACTION_ITEM_SEX:
// JA2Gold: Disable brothel sex Madd: Re-enabled
if ( ! (gTacticalStatus.uiFlags & INCOMBAT) )
{
UINT8 ubID;
OBJECTTYPE DoorCloser;
INT16 sTeleportSpot;
INT16 sDoorSpot;
UINT8 ubDirection;
//DBrot: More Rooms
//UINT8 ubRoom, ubOldRoom;
UINT16 usRoom, usOldRoom;
ubID = WhoIsThere2( sGridNo, 0 );
if ( (ubID != NOBODY) && (MercPtrs[ ubID ]->bTeam == gbPlayerNum) )
{
if ( InARoom( sGridNo, &usRoom ) && InARoom( MercPtrs[ ubID ]->sOldGridNo, &usOldRoom ) && usOldRoom != usRoom )
{
// also require there to be a miniskirt civ in the room
if ( HookerInRoom( usRoom ) )
{
// stop the merc...
MercPtrs[ ubID ]->EVENT_StopMerc( MercPtrs[ ubID ]->sGridNo, MercPtrs[ ubID ]->ubDirection );
switch( sGridNo )
{
case 13414:
sDoorSpot = 13413;
sTeleportSpot = 13413;
break;
case 11174:
sDoorSpot = 11173;
sTeleportSpot = 11173;
break;
case 12290:
sDoorSpot = 12290;
sTeleportSpot = 12291;
break;
default:
sDoorSpot = NOWHERE;
sTeleportSpot = NOWHERE;
}
if (!TileIsOutOfBounds(sDoorSpot) && !TileIsOutOfBounds(sTeleportSpot) )
{
// close the door...
DoorCloser[0]->data.misc.bActionValue = ACTION_ITEM_CLOSE_DOOR;
PerformItemAction( sDoorSpot, &DoorCloser );
// have sex
HandleNPCDoAction( 0, NPC_ACTION_SEX, 0 );
// move the merc outside of the room again
sTeleportSpot = FindGridNoFromSweetSpotWithStructData( MercPtrs[ ubID ], STANDING, sTeleportSpot, 2, &ubDirection, FALSE );
MercPtrs[ ubID ]->ChangeSoldierState( STANDING, 0, TRUE );
TeleportSoldier( MercPtrs[ ubID ], sTeleportSpot, FALSE );
HandleMoraleEvent( MercPtrs[ ubID ], MORALE_SEX, gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
FatigueCharacter( MercPtrs[ ubID ] );
FatigueCharacter( MercPtrs[ ubID ] );
FatigueCharacter( MercPtrs[ ubID ] );
FatigueCharacter( MercPtrs[ ubID ] );
DirtyMercPanelInterface( MercPtrs[ ubID ], DIRTYLEVEL1 );
}
}
}
break;
}
}
break;
case ACTION_ITEM_REVEAL_ROOM:
{
//DBrot: More Rooms
//UINT8 ubRoom;
UINT16 usRoom;
if ( InAHiddenRoom( sGridNo, &usRoom ) )
{
RemoveRoomRoof( sGridNo, usRoom, NULL );
}
}
break;
case ACTION_ITEM_LOCAL_ALARM:
MakeNoise( NOBODY, sGridNo, 0, gpWorldLevelData[sGridNo].ubTerrainID, 30, NOISE_SILENT_ALARM );
break;
case ACTION_ITEM_GLOBAL_ALARM:
CallAvailableEnemiesTo( sGridNo );
break;
case ACTION_ITEM_BLOODCAT_ALARM:
CallAvailableTeamEnemiesTo( sGridNo, CREATURE_TEAM );
break;
case ACTION_ITEM_KLAXON:
PlayJA2Sample( KLAXON_ALARM, RATE_11025, SoundVolume( MIDVOLUME, sGridNo ), 5, SoundDir( sGridNo ) );
break;
case ACTION_ITEM_MUSEUM_ALARM:
PlayJA2Sample( KLAXON_ALARM, RATE_11025, SoundVolume( MIDVOLUME, sGridNo ), 5, SoundDir( sGridNo ) );
CallEldinTo( sGridNo );
break;
#ifdef JA2UB
case ACTION_ITEM_BIGGENS_BOMBS:
if( ShouldThePlayerStopWhenWalkingOnBiggensActionItem( 17 ) )
{
HavePersonAtGridnoStop( sGridNo );
//Make Biggens run for cover and then detonate the explosives
TriggerNPCRecord( BIGGENS_UB , 17 ); //BIGGENS
}
break;
case ACTION_ITEM_BIGGENS_WARNING:
if( ShouldThePlayerStopWhenWalkingOnBiggensActionItem( 16 ) )
{
HavePersonAtGridnoStop( sGridNo );
//Have Biggens spit out a warning about the bombs
TriggerNPCRecord( BIGGENS_UB, 16 ); //BIGGENS
}
break;
case ACTION_ITEM_SEE_FORTIFIED_DOOR:
HandleSeeingFortifiedDoor( sGridNo );
break;
case ACTION_ITEM_OPEN_FORTIFED_DOOR:
HandleSwitchToOpenFortifiedDoor( sGridNo );
break;
case ACTION_ITEM_SEE_POWER_GEN_FAN:
//if the player is in the power plant J13
if( gWorldSectorX == gGameUBOptions.SectorFanX && gWorldSectorY == gGameUBOptions.SectorFanY && gbWorldSectorZ == gGameUBOptions.SectorFanZ )
{
HandleSeeingPowerGenFan( sGridNo );
}
else if( gWorldSectorX == gGameUBOptions.SectorLaunchMisslesX && gWorldSectorY == gGameUBOptions.SectorLaunchMisslesY && gbWorldSectorZ == gGameUBOptions.SectorLaunchMisslesZ ) //L15-3
{
//The player is hitting the switch to launch the missles
HandlePlayerHittingSwitchToLaunchMissles();
}
break;
#endif
default:
// error message here
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"Action item with invalid action in gridno %d!", sGridNo );
#endif
break;
}
#endif
}
void AddBombToQueue( UINT32 uiWorldBombIndex, UINT32 uiTimeStamp, BOOL fFromRemoteClient )
{
if (gubElementsOnExplosionQueue == MAX_BOMB_QUEUE)
{
return;
}
// 20091002 - OJW - MP Explosives
if (is_networked && is_client)
{
/*if (gWorldBombs[uiWorldBombIndex].bIsFromRemotePlayer && !fFromRemoteClient)
{
return;
}
else
{
// this is the world item index
UINT32 iWorldIndex = gWorldBombs[uiWorldBombIndex].iItemIndex;
WORLDITEM wi = gWorldItems[iWorldIndex];
if (wi.fExists)
{
INT8 soldierID = wi.soldierID;
if (soldierID == -1)
soldierID = wi.object[0]->data.misc.ubBombOwner - 2; // undo the hack
send_detonate_explosive(iWorldIndex,soldierID);
}
}*/
UINT32 iWorldIndex = gWorldBombs[uiWorldBombIndex].iItemIndex;
WORLDITEM wi = gWorldItems[iWorldIndex];
if (wi.fExists)
{
INT8 soldierID = wi.soldierID; // bomb's owner
if (soldierID == -1)
soldierID = wi.object[0]->data.misc.ubBombOwner - 2; // undo the hack
if (IsOurSoldier(gubPersonToSetOffExplosions) || IsOurSoldier(soldierID))
{
// we set off the bomb (could be failed disarm) or we own it, tell the other clients we are setting it off
send_detonate_explosive(iWorldIndex,gubPersonToSetOffExplosions);
}
else if (gWorldBombs[uiWorldBombIndex].bIsFromRemotePlayer && !fFromRemoteClient)
{
return; // dont explode bombs which arent originating from our client unless we were told to
}
}
}
gExplosionQueue[gubElementsOnExplosionQueue].uiWorldBombIndex = uiWorldBombIndex;
gExplosionQueue[gubElementsOnExplosionQueue].uiTimeStamp = uiTimeStamp;
gExplosionQueue[gubElementsOnExplosionQueue].fExists = TRUE;
if (!gfExplosionQueueActive)
{
// lock UI
guiPendingOverrideEvent = LU_BEGINUILOCK;
// disable sight
gTacticalStatus.uiFlags |= DISALLOW_SIGHT;
}
gubElementsOnExplosionQueue++;
gfExplosionQueueActive = TRUE;
}
// Flugente: activate everything connected to a tripwire in the surrounding if sGridNo on level bLevel with regard to the tripwire netwrok and hierarchy determined by ubFlag
BOOLEAN ActivateSurroundingTripwire( UINT8 ubID, INT32 sGridNo, INT8 bLevel, UINT32 ubFlag )
{
UINT32 uiTimeStamp= GetJA2Clock();
BOOLEAN fFoundMine = FALSE;
UINT8 feasibletripwiredirections[4] =
{
NORTH,
EAST,
SOUTH,
WEST
};
// for every orientation
for (UINT8 i = 0; i < 4; ++i)
{
UINT8 direction = feasibletripwiredirections[i];
// get adjacent grid
UINT32 adjgrid = NewGridNo( sGridNo, DirectionInc( direction ) );
// 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 == adjgrid && gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].ubLevel == bLevel )
{
OBJECTTYPE* pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if (!((*pObj).fFlags & OBJECT_DISABLED_BOMB))
{
// Flugente: we can either activate a bomb, or an action item that simulates a bomb - we have to check that first
UINT16 usBombItem = pObj->usItem;
if ( usBombItem == ACTION_ITEM )
usBombItem = (*pObj)[0]->data.misc.usBombItem;
// if item can be activated by tripwire, detonate it
if ( Item[usBombItem].tripwireactivation == 1 )
{
// tripwire just gets activated
if ( Item[usBombItem].tripwire == 1 )
{
// this is important - we have to check wether the wire has already been activated
if ( ( (*pObj)[0]->data.sObjectFlag & TRIPWIRE_ACTIVATED ) == 0 )
{
// determine this tripwire's flag
UINT32 ubWireNetworkFlag = (*pObj)[0]->data.ubWireNetworkFlag;
// check if a) tripwire belongs to the same tripwire network and b) its of the same or lower hierarchy level
BOOLEAN samenetwork = FALSE;
BOOLEAN sameorlowerhierarchy = FALSE;
// the biggest flags are the hierarchy flags, so a simple comparison is enough here
if ( ubWireNetworkFlag <= ubFlag )
sameorlowerhierarchy = TRUE;
if ( !sameorlowerhierarchy )
continue;
// test: do the 2 wires belong to the same 'side'?
if ( ( (ubFlag & TRIPWIRE_NETWORK_OWNER_ENEMY) && (ubWireNetworkFlag & TRIPWIRE_NETWORK_OWNER_ENEMY) ) || ( (ubFlag & TRIPWIRE_NETWORK_OWNER_PLAYER) && (ubWireNetworkFlag & TRIPWIRE_NETWORK_OWNER_PLAYER) ) )
{
if ( ( (ubFlag & TRIPWIRE_NETWORK_NET_1) && (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_1) ) ||
( (ubFlag & TRIPWIRE_NETWORK_NET_2) && (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_2) ) ||
( (ubFlag & TRIPWIRE_NETWORK_NET_3) && (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_3) ) ||
( (ubFlag & TRIPWIRE_NETWORK_NET_4) && (ubWireNetworkFlag & TRIPWIRE_NETWORK_NET_4) ) )
samenetwork = TRUE;
else
continue;
}
else
continue;
if ( samenetwork && sameorlowerhierarchy )
{
gubPersonToSetOffExplosions = ubID;
/*if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP)
{
uiTimeStamp += BOMB_QUEUE_DELAY;
}*/
// put this bomb on the queue
AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
if ( (*pObj)[0]->data.misc.usBombItem != NOTHING && Item[ (*pObj)[0]->data.misc.usBombItem ].usItemClass & IC_EXPLOSV )
{
fFoundMine = TRUE;
}
// Flugente hack: the tripwire will get removed in HandleExplosionQueue (it is still needed in there until the bomb gets called).
// Because of this, ActivateSurroundingTripwire would normally find this wire again, thus a loop would occur.
// So we mark the wire with this flag
(*pObj)[0]->data.sObjectFlag |= TRIPWIRE_ACTIVATED;
// activate surrounding tripwires, unless tripwire is too much damaged and we are unlucky..
if ( (*pObj)[0]->data.objectStatus > (INT16)Random(50) )
fFoundMine = ActivateSurroundingTripwire(ubID, adjgrid, bLevel, ubWireNetworkFlag);
}
}
}
// bombs go off
else
{
gubPersonToSetOffExplosions = ubID;
// SANDRO - merc records
// only if we blew up somebody not in our team(no achievement for blowing our guys :)), only if owner exists and have profile
if ( (MercPtrs[ubID]->bTeam != gbPlayerNum) && ((*pObj)[0]->data.misc.ubBombOwner > 1) )
{
if ( MercPtrs[ ((*pObj)[0]->data.misc.ubBombOwner - 2) ]->ubProfile != NO_PROFILE && MercPtrs[ ((*pObj)[0]->data.misc.ubBombOwner - 2) ]->bTeam == gbPlayerNum )
gMercProfiles[ MercPtrs[ ((*pObj)[0]->data.misc.ubBombOwner - 2) ]->ubProfile ].records.usExpDetonated++;
}
/*if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP)
{
uiTimeStamp += BOMB_QUEUE_DELAY;
}*/
// put this bomb on the queue
AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
if ( (*pObj)[0]->data.misc.usBombItem != NOTHING && Item[ (*pObj)[0]->data.misc.usBombItem ].usItemClass & IC_EXPLOSV )
{
fFoundMine = TRUE;
}
}
}
}
}
}
}
return( fFoundMine );
}
// Flugente: A special function for tripwire gun traps. Search if pObj has a gun attached. If so, fire a shot from that gun in a specific direction. Afterwards place the gun on the ground
void CheckAndFireTripwireGun( OBJECTTYPE* pObj, INT32 sGridNo, INT8 bLevel, UINT8 ubId, UINT8 ubDirection )
{
if ( !pObj )
return;
// we actually found a gun.
// if the gun has ammo and is not jammed, fire it
if ( (*pObj)[0]->data.gun.ubGunShotsLeft > 0 && (*pObj)[0]->data.gun.bGunAmmoStatus > 0 )
{
// Increment attack counter...
if (gubElementsOnExplosionQueue == 0)
{
// single explosion, disable sight until the end, and set flag
// to check sight at end of attack
gTacticalStatus.uiFlags |= (DISALLOW_SIGHT | CHECK_SIGHT_AT_END_OF_ATTACK);
}
FireFragmentsTrapGun( MercPtrs[ubId], sGridNo, 0, pObj, ubDirection );
// this is important... if not set, the game will remain in a loop
gTacticalStatus.ubAttackBusyCount = 0;
}
// add this gun to the floor
AddItemToPool( sGridNo, pObj, 1, bLevel, 0, -1 );
}
void HandleExplosionQueue( void )
{
UINT32 uiIndex;
UINT32 uiWorldBombIndex;
UINT32 uiCurrentTime;
INT32 sGridNo;
OBJECTTYPE * pObj;
UINT8 ubLevel;
BOOLEAN fAttFound = FALSE;
if ( !gfExplosionQueueActive )
{
return;
}
uiCurrentTime = GetJA2Clock();
// WDS 07/25/2008 - Avoid error where gWorldItems and/or gWorldBombs is nil
//dnl ch75 271013
if (gWorldBombs && !gWorldItems.empty())
{
for ( uiIndex = 0; uiIndex < gubElementsOnExplosionQueue; ++uiIndex )
{
if ( gExplosionQueue[ uiIndex ].fExists && uiCurrentTime >= gExplosionQueue[ uiIndex ].uiTimeStamp )
{
// Set off this bomb now!
// Preliminary assignments:
uiWorldBombIndex = gExplosionQueue[ uiIndex ].uiWorldBombIndex;
pObj = &( gWorldItems[ gWorldBombs[ uiWorldBombIndex ].iItemIndex ].object );
sGridNo = gWorldItems[ gWorldBombs[ uiWorldBombIndex ].iItemIndex ].sGridNo;
ubLevel = gWorldItems[ gWorldBombs[ uiWorldBombIndex ].iItemIndex ].ubLevel;
if (pObj->usItem == ACTION_ITEM && (*pObj)[0]->data.misc.bActionValue != ACTION_ITEM_BLOW_UP)
{
PerformItemAction( sGridNo, pObj );
}
else if ( (*pObj)[0]->data.misc.usBombItem == TRIP_KLAXON )
{
PlayJA2Sample( KLAXON_ALARM, RATE_11025, SoundVolume( MIDVOLUME, sGridNo ), 5, SoundDir( sGridNo ) );
CallAvailableEnemiesTo( sGridNo );
//RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, 0 );
}
else if ( (*pObj)[0]->data.misc.usBombItem == TRIP_FLARE || Item[pObj->usItem].flare)
{
// sevenfm: changed pObj->usItem to Item[pObj->usItem].ubClassIndex as it should be correct explosives index
// NewLightEffect( sGridNo, (UINT8)Explosive[pObj->usItem].ubDuration, (UINT8)Explosive[pObj->usItem].ubStartRadius );
NewLightEffect( sGridNo, (UINT8)Explosive[ Item[pObj->usItem].ubClassIndex ].ubDuration, (UINT8)Explosive[ Item[pObj->usItem].ubClassIndex ].ubStartRadius );
RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, ubLevel );
}
// beartraps don't cause explosions, but cause special leg damage
else if ( HasItemFlag( pObj->usItem, BEARTRAP ) )
{
// play sound
UINT16 ubTypeID = Explosive[Item[pObj->usItem].ubClassIndex].ubAnimationID;
INT32 sSoundID = gExpAniData[ubTypeID].sExplosionSoundID;
// Randomize
if ( gExpAniData[ubTypeID].sAltExplosionSoundID > NO_ALT_SOUND && sSoundID < 0 )
sSoundID = gExpAniData[ubTypeID].sAltExplosionSoundID;
if ( sSoundID > NO_ALT_SOUND )
PlayJA2Sample( sSoundID, RATE_11025, SoundVolume( HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
UINT8 ubID = WhoIsThere2( sGridNo, ubLevel );
if ( ubID != NOBODY )
{
SOLDIERTYPE* pSoldier = MercPtrs[ubID];
INT16 damage = Explosive[Item[pObj->usItem].ubClassIndex].ubDamage * 0.67f + Random( Explosive[Item[pObj->usItem].ubClassIndex].ubDamage * 0.67f );
INT16 breathdamage = Explosive[Item[pObj->usItem].ubClassIndex].ubStunDamage * 0.67f + Random( Explosive[Item[pObj->usItem].ubClassIndex].ubStunDamage * 0.67f );
pSoldier->SoldierTakeDamage( 0, damage, breathdamage, TAKE_DAMAGE_EXPLOSION, NOBODY, sGridNo, 0, TRUE );
// play additional 'hit' sound
PlayJA2SampleFromFile( "Sounds\\beartrap_fleshhit.wav", RATE_11025, SoundVolume( HIGHVOLUME, sGridNo ), 1, SoundDir( sGridNo ) );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s activated %s!", pSoldier->GetName( ), Item[pObj->usItem].szItemName );
DeductPoints( pSoldier, 200, 0 );
SoldierCollapse( pSoldier );
// Flugente: disease
HandlePossibleInfection( pSoldier, NULL, INFECTION_TYPE_WOUND_AGI, 1.5f );
HandlePossibleInfection( pSoldier, NULL, INFECTION_TYPE_WOUND_AGI, 1.5f );
INT8 bStatLoss = min( 3 + Random( 5 ) + Random( 8 ), pSoldier->stats.bAgility - 1 );
if ( bStatLoss > 0 )
{
pSoldier->stats.bAgility -= bStatLoss;
// SANDRO - added this for healing lost stats feature
pSoldier->ubCriticalStatDamage[DAMAGED_STAT_AGILITY] += bStatLoss;
if ( pSoldier->ubProfile != NO_PROFILE )
{
gMercProfiles[pSoldier->ubProfile].bAgility = pSoldier->stats.bAgility;
}
if ( pSoldier->name[0] && pSoldier->bVisible == TRUE )
{
// make stat RED for a while...
pSoldier->timeChanges.uiChangeAgilityTime = GetJA2Clock( );
pSoldier->usValueGoneUp &= ~(AGIL_INCREASE);
if ( bStatLoss == 1 )
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_1_AGIL], pSoldier->GetName( ) );
}
else
{
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_LOSES_AGIL], pSoldier->GetName( ), bStatLoss );
}
}
}
}
// object can be used again
{
OBJECTTYPE newObject;
CreateItem( (*pObj)[0]->data.misc.usBombItem, (*pObj)[0]->data.objectStatus, &newObject );
// this is important: delete the tripwire, otherwise we get into an infinite loop if there are two piecs of tripwire....
RemoveItemFromPool( sGridNo, gWorldBombs[uiWorldBombIndex].iItemIndex, ubLevel );
// if no other bomb exists here
CheckForBuriedBombsAndRemoveFlags( sGridNo, ubLevel );
// delete the flag, otherwise wire will only work once
(newObject)[0]->data.sObjectFlag &= ~TRIPWIRE_ACTIVATED;
// now add a tripwire item to the floor, simulating that activating tripwire deactivates it
AddItemToPool( sGridNo, &newObject, 1, ubLevel, 0, -1 );
}
}
// Flugente: handle tripwire gun traps here...
// tripwire gets called and activated in ActivateSurroundingTripwire
else if ( Item[pObj->usItem].tripwire == 1 )
{
OBJECTTYPE newtripwireObject;
CreateItem( pObj->usItem, (*pObj)[0]->data.objectStatus, &newtripwireObject );
// search for attached guns
BOOLEAN fgunfound = FALSE;
OBJECTTYPE* pAttGun = NULL;
// check all attachments
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;
pAttGun = &(*iter);
break;
}
}
if ( fgunfound && pAttGun && pAttGun->exists() )
{
// Flugente 2012-06-16: I ran into a weird bug. If activating multiple guntraps, an error would occur when placing a gun on the floor.
// The problem is the function GetFreeWorldItemIndex( ) in AddItemToWorld() in World Items.cpp.
// It gets a free index for the item that should be placed. If there is no free index, it creates a bigger array, copies the current array into it, and then deletes the old index.
// For some reason still unknown to me, this corrupts the pObj-pointer. It also corrupts the pAttGun-pointer, this is the reason I create a new object.
// I had to move some functions here to take care of that. It would be good if someone with more knowledge could take a look at this.
// fire with this gun, if possible. Afterwards place it on the floor
OBJECTTYPE object(*pAttGun);
CheckAndFireTripwireGun( &object, sGridNo, ubLevel, (*pObj)[0]->data.misc.ubBombOwner, (*pObj)[0]->data.ubDirection );
}
else
{
// sevenfm: blow attached items with tripwireactivation = TRUE
// no preplaced (owner=NOBODY) tripwire with explosive attachments allowed
if ( gGameExternalOptions.bAllowExplosiveAttachments && (*pObj)[0]->data.misc.ubBombOwner > 1 )
{
fAttFound=HandleAttachedExplosions( (UINT8) ((*pObj)[0]->data.misc.ubBombOwner - 2), CenterX( sGridNo ), CenterY( sGridNo ), 0,
sGridNo, (*pObj)[0]->data.misc.usBombItem, FALSE, ubLevel, (*pObj)[0]->data.ubDirection, pObj);
}
}
// this is important: delete the tripwire, otherwise we get into an infinite loop if there are two piecs of tripwire....
RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, ubLevel );
// if no other bomb exists here
CheckForBuriedBombsAndRemoveFlags( sGridNo, ubLevel );
// delete the flag, otherwise wire will only work once
(newtripwireObject)[0]->data.sObjectFlag &= ~TRIPWIRE_ACTIVATED;
// now add a tripwire item to the floor, simulating that activating tripwire deactivates it
AddItemToPool( sGridNo, &newtripwireObject, 1, ubLevel, 0, -1 );
}
// Flugente: handle tripwire gun traps here...
// tripwire gets called and activated in ActivateSurroundingTripwire
// now for action item tripwire
else if ( pObj->usItem == ACTION_ITEM && (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP && Item[(*pObj)[0]->data.misc.usBombItem].tripwire == 1 )
{
OBJECTTYPE newtripwireObject;
CreateItem( (*pObj)[0]->data.misc.usBombItem, (*pObj)[0]->data.objectStatus, &newtripwireObject );
// this is important: delete the tripwire, otherwise we get into an infinite loop if there are two piecs of tripwire....
RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, ubLevel );
// if no other bomb exists here
CheckForBuriedBombsAndRemoveFlags( sGridNo, ubLevel );
// delete the flag, otherwise wire will only work once
(newtripwireObject)[0]->data.sObjectFlag &= ~TRIPWIRE_ACTIVATED;
// now add a tripwire item to the floor, simulating that activating tripwire deactivates it
AddItemToPool( sGridNo, &newtripwireObject, 1, ubLevel, 0, -1 );
}
else if ( (*pObj)[0]->data.ubWireNetworkFlag & ANY_ARTILLERY_FLAG )
{
UINT8 cnt = 0;
if ( (*pObj)[0]->data.ubWireNetworkFlag & ARTILLERY_STRIKE_COUNT_1 ) cnt += 1;
if ( (*pObj)[0]->data.ubWireNetworkFlag & ARTILLERY_STRIKE_COUNT_2 ) cnt += 2;
if ( (*pObj)[0]->data.ubWireNetworkFlag & ARTILLERY_STRIKE_COUNT_4 ) cnt += 4;
// determine gridno to attack - smoke signal required. Otherwise, it is assumed the radio operator ordered the bombing of his OWN position
// if we cannot even find a radio operator, all bets are off - target a random gridno
// the usual +/- 2 shenanigans
UINT16 usOwner = NOBODY;
if ( (*pObj)[0]->data.misc.ubBombOwner > 1 )
usOwner = (*pObj)[0]->data.misc.ubBombOwner - 2;
INT32 sTargetGridNo = NOWHERE;
if ( GetRandomSignalSmokeGridNo( &sTargetGridNo ) || GetRadioOperatorSignal( usOwner, &sTargetGridNo ) || (sTargetGridNo = RandomGridNo( )) )
{
for ( UINT8 i = 0; i < cnt; ++i)
ArtilleryStrike( pObj->usItem, usOwner, sGridNo, sTargetGridNo );
}
// not needed anymore
RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, ubLevel );
}
else
{
gfExplosionQueueMayHaveChangedSight = TRUE;
// We have to remove the item first to prevent the explosion from detonating it
// a second time :-)
RemoveItemFromPool( sGridNo, gWorldBombs[ uiWorldBombIndex ].iItemIndex, ubLevel );
// make sure no one thinks there is a bomb here any more!
CheckForBuriedBombsAndRemoveFlags( sGridNo, ubLevel);
// BOOM!
// bomb objects only store the SIDE who placed the bomb! :-(
if ( (*pObj)[0]->data.misc.ubBombOwner > 1 )
{
IgniteExplosion( (UINT8) ((*pObj)[0]->data.misc.ubBombOwner - 2), CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, (*pObj)[0]->data.misc.usBombItem, ubLevel, (*pObj)[0]->data.ubDirection, pObj);
}
else
{
// pre-placed
IgniteExplosion( NOBODY, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, (*pObj)[0]->data.misc.usBombItem, ubLevel, (*pObj)[0]->data.ubDirection );
}
}
/* if ( FindWorldItemForBuriedBombInGridNo(sGridNo, ubLevel) != -1 )
{
gpWorldLevelData[sGridNo].uiFlags |= MAPELEMENT_PLAYER_MINE_PRESENT;
gpWorldLevelData[sGridNo].uiFlags |= MAPELEMENT_ENEMY_MINE_PRESENT;
}*/
// Bye bye bomb
gExplosionQueue[ uiIndex ].fExists = FALSE;
}
}
}
// See if we can reduce the # of elements on the queue that we have recorded
// Easier to do it at this time rather than in the loop above
while ( gubElementsOnExplosionQueue > 0 && gExplosionQueue[ gubElementsOnExplosionQueue - 1 ].fExists == FALSE )
{
gubElementsOnExplosionQueue--;
}
if ( gubElementsOnExplosionQueue == 0 && (gubPersonToSetOffExplosions == NOBODY || gTacticalStatus.ubAttackBusyCount == 0) )
{
// turn off explosion queue
// re-enable sight
gTacticalStatus.uiFlags &= (~DISALLOW_SIGHT);
if ( gubPersonToSetOffExplosions != NOBODY && !(MercPtrs[ gubPersonToSetOffExplosions ]->flags.uiStatusFlags & SOLDIER_PC) )
{
FreeUpNPCFromPendingAction( MercPtrs[ gubPersonToSetOffExplosions ] );
}
if (gfExplosionQueueMayHaveChangedSight)
{
UINT8 ubLoop;
SOLDIERTYPE * pTeamSoldier;
// set variable so we may at least have someone to resolve interrupts vs
gubInterruptProvoker = gubPersonToSetOffExplosions;
AllTeamsLookForAll( TRUE );
// call fov code
ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID;
for ( pTeamSoldier = MercPtrs[ ubLoop ]; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ubLoop++, pTeamSoldier++ )
{
if ( pTeamSoldier->bActive && pTeamSoldier->bInSector )
{
RevealRoofsAndItems( pTeamSoldier, TRUE, FALSE, pTeamSoldier->pathing.bLevel, FALSE );
}
}
gfExplosionQueueMayHaveChangedSight = FALSE;
gubPersonToSetOffExplosions = NOBODY;
}
// unlock UI
//UnSetUIBusy( (UINT8)gusSelectedSoldier );
// OJW - 20091028 - fix explosion UI lock bug on unoriginating clients
if ( !(gTacticalStatus.uiFlags & INCOMBAT) || gTacticalStatus.ubCurrentTeam == gbPlayerNum || (is_networked && gTacticalStatus.ubCurrentTeam != 1) )
{
// don't end UI lock when it's a computer turn
guiPendingOverrideEvent = LU_ENDUILOCK;
}
gfExplosionQueueActive = FALSE;
}
}
extern DragSelection gDragSelection;
// Flugente: show warnings around armed timebombs both in map and inventories
void HandleExplosionWarningAnimations( )
{
if ( gGameExternalOptions.fTimeBombWarnAnimations )
{
OBJECTTYPE * pObj;
// Go through all the bombs in the world, and look for timed ones
for ( UINT32 uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; ++uiWorldBombIndex )
{
if ( gWorldBombs[uiWorldBombIndex].fExists )
{
pObj = &(gWorldItems[gWorldBombs[uiWorldBombIndex].iItemIndex].object);
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_TIMED && !((*pObj).fFlags & OBJECT_DISABLED_BOMB) && (*pObj)[0]->data.misc.bDelay )
{
DrawExplosionWarning( gWorldItems[gWorldBombs[uiWorldBombIndex].iItemIndex].sGridNo, gWorldItems[gWorldBombs[uiWorldBombIndex].iItemIndex].ubLevel, (*pObj)[0]->data.misc.bDelay );
}
}
}
// Flugente: we have to check every inventory for armed bombs and do the countdown for them, too
for ( UINT32 cnt = 0; cnt < guiNumMercSlots; ++cnt )
{
SOLDIERTYPE* pSoldier = MercSlots[cnt];
if ( pSoldier != NULL )
{
if ( pSoldier->bInSector && pSoldier->bActive && (pSoldier->sSectorX == gWorldSectorX) && (pSoldier->sSectorY == gWorldSectorY) && (pSoldier->bSectorZ == gbWorldSectorZ) )
{
INT8 invsize = (INT8)pSoldier->inv.size( ); // remember inventorysize, so we don't call size() repeatedly
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop ) // ... for all items in our inventory ...
{
// ... if Item is a bomb ...
if ( pSoldier->inv[bLoop].exists( ) && (Item[pSoldier->inv[bLoop].usItem].usItemClass & (IC_BOMB | IC_GRENADE)) )
{
OBJECTTYPE * pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ...
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_TIMED && (*pObj)[0]->data.misc.bDelay )
{
DrawExplosionWarning( pSoldier->sGridNo, pSoldier->pathing.bLevel, (*pObj)[0]->data.misc.bDelay );
break;
}
}
}
}
}
}
}
UINT16 usBlue = Get16BPPColor(FROMRGB(0, 0, 255));
UINT16 usColor;
INT32 sRadius;
// show focus area if skill is active
if ( gusSelectedSoldier != NOBODY )
{
SOLDIERTYPE* pSoldier = MercPtrs[gusSelectedSoldier];
if ( pSoldier->bActive && pSoldier->bInSector)
{
if (pSoldier->usSoldierFlagMask2 & SOLDIER_TRAIT_FOCUS)
{
// checking whether this skill is active is relatively cheap. If it fails, deactivate skill properly
// we cannot use CanUseSkill(...) again though, as this would fail upon opening the menu
if (pSoldier->CanUseSkill(SKILLS_FOCUS, FALSE, pSoldier->sFocusGridNo))
{
// radius depends on range
INT16 range = PythSpacesAway(pSoldier->sFocusGridNo, pSoldier->sGridNo);
INT16 radius = gSkillTraitValues.ubSNFocusRadius * range / 20;
DrawTraitRadius(pSoldier->sFocusGridNo, pSoldier->pathing.bLevel, sqrt(0.5) * (20 + 40 * radius), 8, usBlue);
}
else
{
// if conditions don't apply, deactivate skill. This will cause it to update to status changes very fast
pSoldier->usSoldierFlagMask2 &= ~SOLDIER_TRAIT_FOCUS;
pSoldier->sFocusGridNo = NOWHERE;
}
}
// sevenfm: show draggable objects
if (gDragSelection.Active()) // || gCurrentUIMode == HANDCURSOR_MODE)
{
// check all tiles on screen
UINT32 tiletype;
UINT16 structurenumber;
UINT8 hitpoints;
UINT8 decalflag;
INT8 bLevel = pSoldier->pathing.bLevel;
UINT16 usRoomNo;
for (INT32 sSpot = 0; sSpot < WORLD_MAX; sSpot++)
{
if (!TileIsOutOfBounds(sSpot) &&
GridNoOnVisibleWorldTile(sSpot) &&
GridNoOnScreen(sSpot) &&
!TERRAIN_IS_HIGH_WATER(sSpot) &&
!InAHiddenRoom(sSpot, &usRoomNo))
{
BOOLEAN fShow = FALSE;
if (IsDragStructurePresent(sSpot, bLevel, tiletype, structurenumber, hitpoints, decalflag) &&
!Water(sSpot, bLevel) &&
FindNotWaterNearby(sSpot, bLevel))
{
fShow = TRUE;
}
// soldiers
if (!fShow)
{
for (UINT32 ubID = gTacticalStatus.Team[OUR_TEAM].bFirstID; ubID <= gTacticalStatus.Team[CIV_TEAM].bLastID; ++ubID)
{
if (ubID != pSoldier->ubID &&
MercPtrs[ubID] &&
MercPtrs[ubID]->sGridNo == sSpot &&
MercPtrs[ubID]->bVisible == TRUE &&
MercPtrs[ubID]->pathing.bLevel == bLevel &&
gAnimControl[MercPtrs[ubID]->usAnimState].ubEndHeight == ANIM_PRONE &&
!Water(MercPtrs[ubID]->sGridNo, MercPtrs[ubID]->pathing.bLevel) &&
pSoldier->ubBodyType <= REGFEMALE &&
(MercPtrs[ubID]->bTeam == pSoldier->bTeam || MercPtrs[ubID]->IsUnconscious() || MercPtrs[ubID]->stats.bLife < OKLIFE))
{
fShow = TRUE;
}
}
}
// corpses
if (!fShow)
{
ROTTING_CORPSE* pCorpse;
for (INT32 sCorpseID = 0; sCorpseID < giNumRottingCorpse; ++sCorpseID)
{
pCorpse = &(gRottingCorpse[sCorpseID]);
if (pCorpse &&
pCorpse->fActivated &&
pCorpse->def.bLevel == pSoldier->pathing.bLevel &&
pCorpse->def.bVisible == TRUE &&
sSpot == pCorpse->def.sGridNo &&
pCorpse->def.ubBodyType < COW &&
pCorpse->def.ubBodyType != QUEENMONSTER)
{
fShow = TRUE;
}
}
}
if (fShow)
{
usColor = Get16BPPColor(FROMRGB(64, 0, 96));
sRadius = (INT32)(sqrt(0.5) * (20));
if (bLevel > 0)
{
DrawTraitRadius(sSpot, bLevel, sRadius, 2, usColor);
DrawTraitRadius(sSpot, bLevel, sRadius + 4, 2, usColor);
}
else
{
DrawTraitRadius(sSpot, bLevel, sRadius, 2, usColor);
}
}
}
}
}
// show known opponents
if (gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] &&
gTacticalStatus.ubCurrentTeam == gbPlayerNum &&
!gTacticalStatus.fAtLeastOneGuyOnMultiSelect &&
pSoldier->stats.bLife >= OKLIFE &&
!pSoldier->IsUnconscious() &&
IS_MERC_BODY_TYPE(pSoldier) &&
!pSoldier->IsSpotting())
{
SOLDIERTYPE *pOpponent;
INT8 bKnowledge;
INT32 sSpot;
INT8 bLevel;
// look through this man's personal & public opplists for opponents known
for (UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
pOpponent = MercSlots[uiLoop];
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent || !ValidOpponent(pSoldier, pOpponent))
{
continue; // next merc
}
bKnowledge = Knowledge(pSoldier, pOpponent->ubID);
// if this opponent is unknown personally and publicly
if (bKnowledge == NOT_HEARD_OR_SEEN || bKnowledge > SEEN_LAST_TURN || bKnowledge < HEARD_LAST_TURN)
{
continue;
}
// obtain opponent's location and level
sSpot = KnownLocation(pSoldier, pOpponent->ubID);
bLevel = KnownLevel(pSoldier, pOpponent->ubID);
if (TileIsOutOfBounds(sSpot))
{
continue;
}
if (pOpponent->bVisible != -1)
{
continue;
}
// show location
usColor = Get16BPPColor(FROMRGB(96, 96, 96));
sRadius = (INT32)(sqrt(0.5) * (20));
if (bLevel > 0)
{
DrawTraitRadius(sSpot, bLevel, sRadius, 2, usColor);
DrawTraitRadius(sSpot, bLevel, sRadius + 4, 2, usColor);
}
else
{
DrawTraitRadius(sSpot, bLevel, sRadius, 2, usColor);
}
}
}
}
}
}
void DecayBombTimers( void )
{
UINT32 uiWorldBombIndex;
UINT32 uiTimeStamp;
OBJECTTYPE * pObj;
uiTimeStamp = GetJA2Clock();
// Go through all the bombs in the world, and look for timed ones
for (uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; uiWorldBombIndex++)
{
if (gWorldBombs[uiWorldBombIndex].fExists)
{
pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_TIMED && !((*pObj).fFlags & OBJECT_DISABLED_BOMB) )
{
// Found a timed bomb, so decay its delay value and see if it goes off
(*pObj)[0]->data.misc.bDelay--;
if ((*pObj)[0]->data.misc.bDelay == 0)
{
// ATE: CC black magic....
if ( (*pObj)[0]->data.misc.ubBombOwner > 1 )
{
gubPersonToSetOffExplosions = (UINT8) ((*pObj)[0]->data.misc.ubBombOwner - 2);
// SANDRO - merc records - detonating explosives
if ( MercPtrs[ gubPersonToSetOffExplosions ]->ubProfile != NO_PROFILE && MercPtrs[ gubPersonToSetOffExplosions ]->bTeam == gbPlayerNum )
{
gMercProfiles[ MercPtrs[ gubPersonToSetOffExplosions ]->ubProfile ].records.usExpDetonated++;
}
}
else
{
gubPersonToSetOffExplosions = NOBODY;
}
// put this bomb on the queue
AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP)
{
uiTimeStamp += BOMB_QUEUE_DELAY;
}
}
}
}
}
// Flugente: we have to check every inventory for armed bombs and do the countdown for them, too
// Flugente: new stuff: we can now also arm bombs in our inventory, and detonate/defuse those bombs remotely
// So we have to look at every item in every inventory in this sector
for (UINT32 cnt = 0; cnt < guiNumMercSlots; ++cnt )
{
SOLDIERTYPE* pSoldier = MercSlots[ cnt ];
if ( pSoldier != NULL )
{
if ( pSoldier->bInSector && pSoldier->bActive )
{
INT8 invsize = (INT8)pSoldier->inv.size(); // remember inventorysize, so we don't call size() repeatedly
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop) // ... for all items in our inventory ...
{
// ... if Item is a bomb ...
if (pSoldier->inv[bLoop].exists() == true && ( Item[pSoldier->inv[bLoop].usItem].usItemClass & (IC_BOMB|IC_GRENADE) ) )
{
OBJECTTYPE * pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ...
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_TIMED )
{
// Found a timed bomb, so decay its delay value and see if it goes off
(*pObj)[0]->data.misc.bDelay--;
if ((*pObj)[0]->data.misc.bDelay == 0)
{
// ATE: CC black magic....
if ( (*pObj)[0]->data.misc.ubBombOwner > 1 )
{
gubPersonToSetOffExplosions = (UINT8) ((*pObj)[0]->data.misc.ubBombOwner - 2);
// SANDRO - merc records - detonating explosives
if ( MercPtrs[ gubPersonToSetOffExplosions ]->ubProfile != NO_PROFILE && MercPtrs[ gubPersonToSetOffExplosions ]->bTeam == gbPlayerNum )
{
gMercProfiles[ MercPtrs[ gubPersonToSetOffExplosions ]->ubProfile ].records.usExpDetonated++;
}
}
else
{
gubPersonToSetOffExplosions = NOBODY;
}
// ignite explosions manually - this item is not in the WorldBombs-structure, so we can't add it to the queue
IgniteExplosion( gubPersonToSetOffExplosions, pSoldier->sX, pSoldier->sY, (INT16) (gpWorldLevelData[pSoldier->sGridNo].sHeight), pSoldier->sGridNo, pObj->usItem, pSoldier->pathing.bLevel, pSoldier->ubDirection, pObj );
DeleteObj( pObj );
}
}
}
}
}
}
}
}
void SetOffBombsByFrequency( UINT8 ubID, INT8 bFrequency )
{
UINT32 uiWorldBombIndex;
UINT32 uiTimeStamp;
OBJECTTYPE * pObj;
uiTimeStamp = GetJA2Clock();
// Flugente: The remote detonator can now detonate _or_ defuse bombs.
// In order for a bomb do be detonated, it must have a remote detonator attached. If a 'detonate' command was clicked, bFrequency will be out of 1 - 4.
// In order for a bomb do be detonated, it must have a remote defuse attached. If a 'defuse' command was clicked, bFrequency will be out of 5 - 8.
BOOLEAN fDetonate = TRUE;
// if bFrequency is in 5-8, it must have been a 'defuse' command. Change for correct frequency;
if ( bFrequency > 4 && bFrequency < 9)
{
fDetonate = FALSE;
bFrequency -= 4;
}
// Go through all the bombs in the world, and look for remote ones
for (uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; uiWorldBombIndex++)
{
if (gWorldBombs[uiWorldBombIndex].fExists)
{
pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_REMOTE && !((*pObj).fFlags & OBJECT_DISABLED_BOMB) )
{
// Found a remote bomb, so check to see if it has the same frequency
if ( fDetonate ) // detonate bombs
{
// Found a remote bomb, so check to see if it has the same frequency
if ((*pObj)[0]->data.misc.bFrequency == bFrequency)
{
// SANDRO - added merc records and some exp
if ( ((*pObj)[0]->data.misc.ubBombOwner) > 1 )
{
if ( MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->ubProfile != NO_PROFILE &&
MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->bTeam == gbPlayerNum )
{
gMercProfiles[MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->ubProfile].records.usExpDetonated++;
StatChange( MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)], EXPLODEAMT, ( 5 ), FALSE );
}
}
gubPersonToSetOffExplosions = ubID;
// put this bomb on the queue
AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP)
{
uiTimeStamp += BOMB_QUEUE_DELAY;
}
}
}
else // defuse bombs
{
// check for frequency
if ((*pObj)[0]->data.bDefuseFrequency == bFrequency)
{
// check for a defuse
if ( HasAttachmentOfClass(pObj, AC_DEFUSE) )
{
(*pObj)[0]->data.bTrap = 0;
if ( (*pObj).fFlags & OBJECT_KNOWN_TO_BE_TRAPPED )
pObj->fFlags &= ~( OBJECT_KNOWN_TO_BE_TRAPPED );
if ( (*pObj).fFlags & OBJECT_ARMED_BOMB )
pObj->fFlags &= ~( OBJECT_ARMED_BOMB );
if ( !((*pObj).fFlags & OBJECT_DISABLED_BOMB) )
pObj->fFlags |= OBJECT_DISABLED_BOMB ;
INT32 sGridNo = gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].sGridNo;
UINT8 ubLevel = gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].ubLevel;
// OJW - 20091029 - disarm explosives
if (is_networked && is_client)
send_disarm_explosive( sGridNo , gWorldBombs[uiWorldBombIndex].iItemIndex, ubID );
// set back ubWireNetworkFlag and bDefuseFrequency, but not the direction... bomb is still aimed, it is just turned off
(*pObj)[0]->data.ubWireNetworkFlag = 0;
(*pObj)[0]->data.bDefuseFrequency = 0;
(*pObj)[0]->data.misc.bDelay = 0;
(*pObj)[0]->data.misc.bFrequency = 0;
(*pObj)[0]->data.misc.ubBombOwner = NOBODY;
(*pObj)[0]->data.misc.bDetonatorType = 0;
//create a new item: copy the old item
OBJECTTYPE newbombitem( *pObj );
// place item on the floor
AddItemToPool( sGridNo, &newbombitem, 1, ubLevel, 0, -1 );
// remove old item
RemoveItemFromPool( sGridNo, gWorldBombs[uiWorldBombIndex].iItemIndex, ubLevel );
// TODO remove bomb from queue...
}
}
}
}
}
}
// Flugente: new stuff: we can now also arm bombs in our inventory, and detonate/defuse those bombs remotely
// So we have to look at every item in every inventory in this sector
for (UINT32 cnt = 0; cnt < guiNumMercSlots; ++cnt )
{
SOLDIERTYPE* pSoldier = MercSlots[ cnt ];
if ( pSoldier != NULL )
{
if ( pSoldier->bInSector && pSoldier->bActive )
{
INT8 invsize = (INT8)pSoldier->inv.size(); // remember inventorysize, so we don't call size() repeatedly
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop) // ... for all items in our inventory ...
{
// ... if Item is a bomb ...
if (pSoldier->inv[bLoop].exists() == true && ( Item[pSoldier->inv[bLoop].usItem].usItemClass & (IC_BOMB) ) )
{
OBJECTTYPE * pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ...
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_REMOTE )
{
// Found a remote bomb, so check to see if it has the same frequency
if ( fDetonate ) // detonate bombs
{
// Found a remote bomb, so check to see if it has the same frequency
if ((*pObj)[0]->data.misc.bFrequency == bFrequency)
{
// SANDRO - added merc records and some exp
if ( ((*pObj)[0]->data.misc.ubBombOwner) > 1 )
{
if ( MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->ubProfile != NO_PROFILE &&
MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->bTeam == gbPlayerNum )
{
gMercProfiles[MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->ubProfile].records.usExpDetonated++;
StatChange( MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)], EXPLODEAMT, ( 5 ), FALSE );
}
}
gubPersonToSetOffExplosions = ubID;
// ignite explosions manually - this item is not in the WorldBobms-structure, so we can't add it to the queue
IgniteExplosion( ubID, pSoldier->sX, pSoldier->sY, (INT16) (gpWorldLevelData[pSoldier->sGridNo].sHeight), pSoldier->sGridNo, pObj->usItem, pSoldier->pathing.bLevel, pSoldier->ubDirection, pObj );
DeleteObj( pObj );
}
}
else // defuse bombs
{
// check for frequency
if ((*pObj)[0]->data.bDefuseFrequency == bFrequency)
{
// check for a defuse
if ( HasAttachmentOfClass(pObj, AC_DEFUSE) )
{
(*pObj)[0]->data.bTrap = 0;
if ( (*pObj).fFlags & OBJECT_KNOWN_TO_BE_TRAPPED )
pObj->fFlags &= ~( OBJECT_KNOWN_TO_BE_TRAPPED );
if ( (*pObj).fFlags & OBJECT_ARMED_BOMB )
pObj->fFlags &= ~( OBJECT_ARMED_BOMB );
// set back ubWireNetworkFlag and bDefuseFrequency, but not the direction... bomb is still aimed, it is just turned off
(*pObj)[0]->data.ubWireNetworkFlag = 0;
(*pObj)[0]->data.bDefuseFrequency = 0;
(*pObj)[0]->data.misc.bDetonatorType = 0;
(*pObj)[0]->data.misc.bDelay = 0;
(*pObj)[0]->data.misc.bFrequency = 0;
(*pObj)[0]->data.bTrap = 0;
}
}
}
}
}
}
}
}
}
}
void SetOffPanicBombs( UINT8 ubID, INT8 bPanicTrigger )
{
// need to turn off gridnos & flags in gTacticalStatus
gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ] = NOWHERE;
if ( ( TileIsOutOfBounds(gTacticalStatus.sPanicTriggerGridNo[0])) &&
( TileIsOutOfBounds(gTacticalStatus.sPanicTriggerGridNo[1])) &&
( TileIsOutOfBounds(gTacticalStatus.sPanicTriggerGridNo[2])) )
{
gTacticalStatus.fPanicFlags &= ~(PANIC_TRIGGERS_HERE);
}
switch( bPanicTrigger )
{
case 0:
SetOffBombsByFrequency( ubID, PANIC_FREQUENCY );
gTacticalStatus.fPanicFlags &= ~(PANIC_BOMBS_HERE);
break;
case 1:
SetOffBombsByFrequency( ubID, PANIC_FREQUENCY_2 );
break;
case 2:
SetOffBombsByFrequency( ubID, PANIC_FREQUENCY_3 );
break;
default:
break;
}
if ( gTacticalStatus.fPanicFlags )
{
// find a new "closest one"
MakeClosestEnemyChosenOne();
}
}
BOOLEAN SetOffBombsInGridNo( UINT8 ubID, INT32 sGridNo, BOOLEAN fAllBombs, INT8 bLevel )
{
UINT32 uiWorldBombIndex;
UINT32 uiTimeStamp;
OBJECTTYPE * pObj;
BOOLEAN fFoundMine = FALSE;
uiTimeStamp = GetJA2Clock();
// Go through all the bombs in the world, and look for mines at this location
for (uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; uiWorldBombIndex++)
{
if (gWorldBombs[uiWorldBombIndex].fExists && gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].sGridNo == sGridNo && gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].ubLevel == bLevel )
{
pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if (!((*pObj).fFlags & OBJECT_DISABLED_BOMB))
{
if (fAllBombs || (*pObj)[0]->data.misc.bDetonatorType == BOMB_PRESSURE)
{
// Flugente: if this is a anti-tank mine, only detonate it if the person triggering it is (in) a vehicle, or if we detonate everything unconditional
if ( !fAllBombs && ubID != NOBODY && Item[pObj->usItem].antitankmine )
{
// if this is not a vehicle, not a robot and not a tank, don't activate
if ( !(MercPtrs[ubID]->flags.uiStatusFlags & SOLDIER_VEHICLE) && !AM_A_ROBOT( MercPtrs[ubID] ) && !ARMED_VEHICLE( MercPtrs[ubID] ) && !ENEMYROBOT( MercPtrs[ubID] ) )
continue;
}
// Snap: if we do set off our own trap (e.g. by trying to disarm it), we pay!
/*if (!fAllBombs && MercPtrs[ ubID ]->bTeam != gbPlayerNum )
{
// ignore this unless it is a mine, etc which would have to have been placed by the
// player, seeing as how the others are all marked as known to the AI.
if ( !(Item[pObj->usItem].mine || pObj->usItem == TRIP_FLARE || pObj->usItem == TRIP_KLAXON ) )
{
continue;
}
}
// player and militia ignore bombs set by player
if ( (*pObj)[0]->data.misc.ubBombOwner > 1 && (MercPtrs[ ubID ]->bTeam == gbPlayerNum || MercPtrs[ ubID ]->bTeam == MILITIA_TEAM) )
{
continue;
}*/
if (pObj->usItem == SWITCH)
{
// send out a signal to detonate other bombs, rather than this which
// isn't a bomb but a trigger
SetOffBombsByFrequency( ubID, (*pObj)[0]->data.misc.bFrequency );
}
// Flugente: a tripwire activates all other tripwires in connection, and detonates all bombs in connection that are tripwire-activated
else if ( Item[pObj->usItem].tripwire == 1 || (pObj->usItem == ACTION_ITEM && (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP && Item[(*pObj)[0]->data.misc.usBombItem].tripwire == 1 ) )
{
gubPersonToSetOffExplosions = ubID;
/*if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP)
{
uiTimeStamp += BOMB_QUEUE_DELAY;
}*/
// put this bomb on the queue
AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
if ( (*pObj)[0]->data.misc.usBombItem != NOTHING && Item[ (*pObj)[0]->data.misc.usBombItem ].usItemClass & IC_EXPLOSV )
{
fFoundMine = TRUE;
}
// Flugente hack: the tripwire will get removed in HandleExplosionQueue (it is still needed in there until the bomb gets called).
// Because of this, ActivateSurroundingTripwire would normally find this wire again, thus a loop would occur.
// So we mark the wire with this flag
(*pObj)[0]->data.sObjectFlag |= TRIPWIRE_ACTIVATED;
// activate surrounding tripwires and tripwire-activated mines, unless tripwire is too much damaged and we are unlucky..
if ( (*pObj)[0]->data.objectStatus > (INT16)Random(50) )
fFoundMine = ActivateSurroundingTripwire(ubID, sGridNo, bLevel, (*pObj)[0]->data.ubWireNetworkFlag);
}
else
{
gubPersonToSetOffExplosions = ubID;
// SANDRO - merc records
// only if we blew up somebody not in our team(no achievement for blowing our guys :)), only if owner exists and have profile
if ( (MercPtrs[ubID]->bTeam != gbPlayerNum) && ((*pObj)[0]->data.misc.ubBombOwner > 1) )
{
if ( MercPtrs[ ((*pObj)[0]->data.misc.ubBombOwner - 2) ]->ubProfile != NO_PROFILE && MercPtrs[ ((*pObj)[0]->data.misc.ubBombOwner - 2) ]->bTeam == gbPlayerNum )
gMercProfiles[ MercPtrs[ ((*pObj)[0]->data.misc.ubBombOwner - 2) ]->ubProfile ].records.usExpDetonated++;
}
/*if (pObj->usItem != ACTION_ITEM || (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP)
{
uiTimeStamp += BOMB_QUEUE_DELAY;
}*/
// put this bomb on the queue
AddBombToQueue( uiWorldBombIndex, uiTimeStamp );
if ( (*pObj)[0]->data.misc.usBombItem != NOTHING && Item[ (*pObj)[0]->data.misc.usBombItem ].usItemClass & IC_EXPLOSV )
{
fFoundMine = TRUE;
}
}
}
}
}
}
return( fFoundMine );
}
void ActivateSwitchInGridNo( UINT8 ubID, INT32 sGridNo )
{
UINT32 uiWorldBombIndex;
OBJECTTYPE * pObj;
// Go through all the bombs in the world, and look for mines at this location
for (uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; uiWorldBombIndex++)
{
if (gWorldBombs[uiWorldBombIndex].fExists && gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].sGridNo == sGridNo)
{
pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if ( pObj->usItem == SWITCH && ( !((*pObj).fFlags & OBJECT_DISABLED_BOMB) ) && (*pObj)[0]->data.misc.bDetonatorType == BOMB_SWITCH)
{
// send out a signal to detonate other bombs, rather than this which
// isn't a bomb but a trigger
// first set attack busy count to 0 in case of a lingering a.b.c. problem...
// No, not a good idea.
// gTacticalStatus.ubAttackBusyCount = 0;
SetOffBombsByFrequency( ubID, (*pObj)[0]->data.misc.bFrequency );
}
}
}
}
BOOLEAN SaveExplosionTableToSaveGameFile( HWFILE hFile )
{
UINT32 uiNumBytesWritten;
UINT32 uiExplosionCount=0;
UINT32 uiCnt;
//
// Explosion queue Info
//
//Write the number of explosion queues
FileWrite( hFile, &gubElementsOnExplosionQueue, sizeof( UINT32 ), &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( UINT32 ) )
{
FileClose( hFile );
return( FALSE );
}
//loop through and add all the explosions
for( uiCnt=0; uiCnt< MAX_BOMB_QUEUE; uiCnt++)
{
FileWrite( hFile, &gExplosionQueue[ uiCnt ], sizeof( ExplosionQueueElement ), &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( ExplosionQueueElement ) )
{
FileClose( hFile );
return( FALSE );
}
}
//
// Explosion Data
//
//loop through and count all the active explosions
uiExplosionCount = 0;
for( uiCnt=0; uiCnt< NUM_EXPLOSION_SLOTS; uiCnt++)
{
if( gExplosionData[ uiCnt ].fAllocated )
{
uiExplosionCount++;
}
}
//Save the number of explosions
FileWrite( hFile, &uiExplosionCount, sizeof( UINT32 ), &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( UINT32 ) )
{
FileClose( hFile );
return( FALSE );
}
//loop through and count all the active explosions
for( uiCnt=0; uiCnt< NUM_EXPLOSION_SLOTS; uiCnt++)
{
if( gExplosionData[ uiCnt ].fAllocated )
{
FileWrite( hFile, &gExplosionData[ uiCnt ], sizeof( EXPLOSIONTYPE ), &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( EXPLOSIONTYPE ) )
{
FileClose( hFile );
return( FALSE );
}
}
}
return( TRUE );
}
BOOLEAN LoadExplosionTableFromSavedGameFile( HWFILE hFile )
{
UINT32 uiNumBytesRead;
UINT32 uiCnt;
//
// Explosion Queue
//
//Clear the Explosion queue
memset( gExplosionQueue, 0, sizeof( ExplosionQueueElement ) * MAX_BOMB_QUEUE );
//Read the number of explosions queue's
FileRead( hFile, &gubElementsOnExplosionQueue, sizeof( UINT32 ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( UINT32 ) )
{
return( FALSE );
}
//loop through read all the active explosions fro the file
for( uiCnt=0; uiCnt<MAX_BOMB_QUEUE; uiCnt++)
{
FileRead( hFile, &gExplosionQueue[ uiCnt ], sizeof( ExplosionQueueElement ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( ExplosionQueueElement ) )
{
return( FALSE );
}
}
//
// Explosion Data
//
//Load the number of explosions
FileRead( hFile, &guiNumExplosions, sizeof( UINT32 ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( UINT32 ) )
{
return( FALSE );
}
//loop through and load all the active explosions
for( uiCnt=0; uiCnt< guiNumExplosions; uiCnt++)
{
FileRead( hFile, &gExplosionData[ uiCnt ], sizeof( EXPLOSIONTYPE ), &uiNumBytesRead );
if( uiNumBytesRead != sizeof( EXPLOSIONTYPE ) )
{
return( FALSE );
}
gExplosionData[ uiCnt ].iID = uiCnt;
gExplosionData[ uiCnt ].iLightID = -1;
GenerateExplosionFromExplosionPointer( &gExplosionData[ uiCnt ] );
}
return( TRUE );
}
BOOLEAN DoesSAMExistHere( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, INT32 sGridNo )
{
INT32 cnt;
INT16 sSectorNo;
// ATE: If we are belwo, return right away...
if ( sSectorZ != 0 )
{
return( FALSE );
}
sSectorNo = SECTOR( sSectorX, sSectorY );
for ( cnt = 0; cnt < NUMBER_OF_SAMS; cnt++ )
{
// Are we i nthe same sector...
if ( pSamList[ cnt ] == sSectorNo )
{
// Are we in the same gridno?
if ( pSamGridNoAList[ cnt ] == sGridNo || pSamGridNoBList[ cnt ] == sGridNo )
{
return( TRUE );
}
}
}
return( FALSE );
}
void UpdateAndDamageSAMIfFound( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, INT32 sGridNo, UINT8 ubDamage )
{
INT16 sSectorNo;
// OK, First check if SAM exists, and if not, return
if ( !DoesSAMExistHere( sSectorX, sSectorY, sSectorZ, sGridNo ) )
{
return;
}
// Damage.....
sSectorNo = CALCULATE_STRATEGIC_INDEX( sSectorX, sSectorY );
INT8 statusbefore = StrategicMap[ sSectorNo ].bSAMCondition;
if ( StrategicMap[ sSectorNo ].bSAMCondition >= ubDamage )
{
StrategicMap[ sSectorNo ].bSAMCondition -= ubDamage;
}
else
{
StrategicMap[ sSectorNo ].bSAMCondition = 0;
}
// SAM site may have been put out of commission...
UpdateAirspaceControl( );
// Flugente: campaign stats: if SAM was functional before and isn't anymore, note this
if ( statusbefore >= MIN_CONDITION_FOR_SAM_SITE_TO_WORK && StrategicMap[ sSectorNo ].bSAMCondition < MIN_CONDITION_FOR_SAM_SITE_TO_WORK )
gCurrentIncident.usIncidentFlags |= INCIDENT_SAMSITE_SABOTAGED;
// ATE: GRAPHICS UPDATE WILL GET DONE VIA NORMAL EXPLOSION CODE.....
}
void UpdateSAMDoneRepair( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ )
{
INT32 cnt;
INT16 sSectorNo;
BOOLEAN fInSector = FALSE;
UINT16 usGoodGraphic, usDamagedGraphic;
// ATE: If we are below, return right away...
if ( sSectorZ != 0 )
{
return;
}
if ( sSectorX == gWorldSectorX && sSectorY == gWorldSectorY && sSectorZ == gbWorldSectorZ )
{
fInSector = TRUE;
}
sSectorNo = SECTOR( sSectorX, sSectorY );
for ( cnt = 0; cnt < NUMBER_OF_SAMS; cnt++ )
{
// Are we i nthe same sector...
if ( pSamList[ cnt ] == sSectorNo )
{
// get graphic.......
GetTileIndexFromTypeSubIndex( EIGHTISTRUCT, (UINT16)( gbSAMGraphicList[ cnt ] ), &usGoodGraphic );
// Damaged one ( current ) is 2 less...
usDamagedGraphic = usGoodGraphic - 2;
// First gridno listed is base gridno....
// if this is loaded....
if ( fInSector )
{
// Update graphic.....
// Remove old!
ApplyMapChangesToMapTempFile( TRUE );
RemoveStruct( pSamGridNoAList[ cnt ], usDamagedGraphic );
AddStructToHead( pSamGridNoAList[ cnt ], usGoodGraphic );
ApplyMapChangesToMapTempFile( FALSE );
}
else
{
// We add temp changes to map not loaded....
// Remove old
RemoveStructFromUnLoadedMapTempFile( pSamGridNoAList[ cnt ], usDamagedGraphic, sSectorX, sSectorY, (UINT8)sSectorZ );
// Add new
AddStructToUnLoadedMapTempFile( pSamGridNoAList[ cnt ], usGoodGraphic, sSectorX, sSectorY, (UINT8)sSectorZ );
}
}
}
// SAM site may have been put back into working order...
UpdateAirspaceControl( );
}
// loop through civ team and find
// anybody who is an NPC and
// see if they get angry
void HandleBuldingDestruction( INT32 sGridNo, UINT8 ubOwner )
{
SOLDIERTYPE * pSoldier;
UINT8 cnt;
if ( ubOwner == NOBODY )
{
return;
}
if ( MercPtrs[ ubOwner ]->bTeam != gbPlayerNum )
{
return;
}
// no point doing this if this is not part of a room
if ( sGridNo == NOWHERE || !InARoom( sGridNo, NULL ) )
return;
cnt = gTacticalStatus.Team[ CIV_TEAM ].bFirstID;
for ( pSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ CIV_TEAM ].bLastID; cnt++ ,pSoldier++ )
{
if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife && pSoldier->aiData.bNeutral )
{
if ( pSoldier->ubProfile != NO_PROFILE )
{
// ignore if the player is fighting the enemy here and this is a good guy
if ( gTacticalStatus.Team[ ENEMY_TEAM ].bMenInSector > 0 && (gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags3 & PROFILE_MISC_FLAG3_GOODGUY) )
{
continue;
}
if ( DoesNPCOwnBuilding( pSoldier, sGridNo ) )
{
MakeNPCGrumpyForMinorOffense( pSoldier, MercPtrs[ ubOwner ] );
}
}
}
}
}
INT32 FindActiveTimedBomb( void )
{
OBJECTTYPE * pObj;
//UINT32 uiTimeStamp = GetJA2Clock();
// Go through all the bombs in the world, and look for timed ones
for ( UINT32 uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; ++uiWorldBombIndex)
{
if (gWorldBombs[uiWorldBombIndex].fExists)
{
pObj = &( gWorldItems[ gWorldBombs[uiWorldBombIndex].iItemIndex ].object );
if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_TIMED && !((*pObj).fFlags & OBJECT_DISABLED_BOMB) )
{
return( gWorldBombs[uiWorldBombIndex].iItemIndex );
}
}
}
return( -1 );
}
BOOLEAN ActiveTimedBombExists( void )
{
if ( gfWorldLoaded )
{
return( FindActiveTimedBomb() != -1 );
}
return( FALSE );
}
void RemoveAllActiveTimedBombs( void )
{
INT32 iItemIndex;
do
{
iItemIndex = FindActiveTimedBomb();
if (iItemIndex != -1 )
{
RemoveItemFromWorld( iItemIndex );
}
} while( iItemIndex != -1 );
}
// Flugente: remove any marker that an explosion is active but has not yet occured (to be used when leaving a sector)
void RemoveActiveExplosionMarkers( )
{
// it is enough to set the fExists-bit to 0 and to then set gubElementsOnExplosionQueue to 0. We don't need to null the structure itself
for ( UINT8 uiIndex = 0; uiIndex < gubElementsOnExplosionQueue; ++uiIndex )
{
gExplosionQueue[uiIndex].fExists = 0;
}
gubElementsOnExplosionQueue = 0;
}
UINT8 DetermineFlashbangEffect( SOLDIERTYPE *pSoldier, INT8 ubExplosionDir, BOOLEAN fInBuilding)
{
INT8 bNumTurns;
UINT16 usHeadItem1, usHeadItem2;
bNumTurns = FindNumTurnsBetweenDirs(pSoldier->ubDirection, ubExplosionDir);
usHeadItem1 = pSoldier->inv[ HEAD1POS ].usItem;
usHeadItem2 = pSoldier->inv[ HEAD2POS ].usItem;
// if soldier got in explosion area check if he is affected by flash
// if soldier wears sun goggles OR grenade behind him OR
// (he is not underground AND it is day AND he is outdoor)
if ( (usHeadItem1 == SUNGOGGLES || usHeadItem2 == SUNGOGGLES) || (bNumTurns > 1) ||
(!gbWorldSectorZ && !NightTime() && !fInBuilding) )
{
// soldier didn't see flash or wears protective sungogles or outdoor at day, so he is only deafened
return ( FIRE_WEAPON_DEAFENED );
}
return ( FIRE_WEAPON_BLINDED_AND_DEAFENED );
}
// HEADROCK HAM 5.1: This handles launching fragments out of an explosion. The number of fragments is read from
// the Explosives.XML file, and they each have a set amount of damage and range as well. They are currently
// fired at completely random trajectories.
void FireFragments( UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, UINT16 usItem, UINT8 ubDirection )
{
// WANNE: Disable Fragments in a multiplayer game, because this can lead to crashes when stepping on mines, ...
if (is_networked)
return;
UINT16 usNumFragments = Explosive[Item[usItem].ubClassIndex].usNumFragments;
UINT16 ubFragRange = Explosive[Item[usItem].ubClassIndex].ubFragRange;
AssertMsg( ubFragRange > 0 , "Fragmentation data lacks range property!" );
// on some maps, the floor height is not 0 (example is Drassen D13). We have to account for that with an offset
INT32 gridno = GETWORLDINDEXFROMWORLDCOORDS( sY, sX );
UINT32 z_offset = CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[gridno].sHeight );
for (UINT16 x = 0; x < usNumFragments; ++x)
{
FLOAT dRandomX = 0;
FLOAT dRandomY = 0;
FLOAT dRandomZ = 0;
// if explosive is directional, calculation of frags is different
if ( Item[usItem].directional == TRUE )
{
// Flugente: if item is a directional explosive, determine in what direction the frags should fly
INT16 degree = (45 + ubDirection * 45) % 360; // modulo 360 to prevent nonsense from nonsensical input
INT16 horizontalarc = Explosive[Item[usItem].ubClassIndex].ubHorizontalDegree % 360; // modulo 360 to prevent nonsense from nonsensical input
INT16 halfhorizontalarc = (INT16)(horizontalarc / 2);
INT16 sLowHorizontalD = (360 + degree - halfhorizontalarc) % 360;
INT16 dRandomDegreeH = (sLowHorizontalD + Random(horizontalarc) ) % 360;
// transform the degree into our coordinates
if ( dRandomDegreeH < 90 )
{
dRandomX = (FLOAT)dRandomDegreeH / 45.0f;
dRandomY = 0.0;
}
else if ( dRandomDegreeH < 180 )
{
dRandomX = (FLOAT)2.0;
dRandomY = (FLOAT)(dRandomDegreeH - 90 ) / 45.0f;
}
else if ( dRandomDegreeH < 270 )
{
dRandomX = (FLOAT)(270 - dRandomDegreeH) / 45.0f;
dRandomY = (FLOAT)2.0;
}
else
{
dRandomX = 0.0;
dRandomY = (FLOAT)(360 - dRandomDegreeH) / 45.0f;
}
// X and Y now need to be distributed, at the moment they are on a circle
// project into [-1.0, 1.0]
dRandomX -= 1.0f;
dRandomY -= 1.0f;
// vertical stuff
INT16 verticalarc = Explosive[Item[usItem].ubClassIndex].ubVerticalDegree % 180; // modulo 180 to prevent nonsense from nonsensical input
INT16 halfverticalarc = (INT16)(verticalarc / 2);
INT16 sLowVerticalD = (90 - halfverticalarc) % 180;
INT16 dRandomDegreeV = sLowVerticalD + Random(verticalarc);
dRandomZ = ((FLOAT)(dRandomDegreeV) / 90.0f) - 1.0f;
}
else
{
dRandomX = ((FLOAT)Random(2000) / 1000.0f) - 1.0f;
dRandomY = ((FLOAT)Random(2000) / 1000.0f) - 1.0f;
dRandomZ = ((FLOAT)Random(2000) / 1000.0f) - 1.0f;
}
FLOAT dDeltaX = (dRandomX * ubFragRange);
FLOAT dDeltaY = (dRandomY * ubFragRange);
FLOAT dDeltaZ = ((dRandomZ * 25.6f) * 50 );
// silversurfer: changed that from 10 to 1 because ubFragRange is already in tiles * 10. A Claymore for example has ubFragRange = 100. Multiplying again by 10 means 100 tiles range!
FLOAT dRangeMultiplier = 1; // Arbitrary, but gives good results.
FLOAT dEndX = (FLOAT)(sX + (dDeltaX * dRangeMultiplier));
FLOAT dEndY = (FLOAT)(sY + (dDeltaY * dRangeMultiplier));
FLOAT dEndZ = (FLOAT)(sZ + (dDeltaZ * dRangeMultiplier));
// Add some randomness to the start coordinates as well, so that not all fragments fly from the same point
// in space.
FLOAT dStartX = (FLOAT)sX + (dRandomX * ((FLOAT)Random(4)+1.0f));
FLOAT dStartY = (FLOAT)sY + (dRandomY * ((FLOAT)Random(4)+1.0f));
FLOAT dStartZ = (FLOAT)sZ + (dRandomZ * ((FLOAT)Random(4)+1.0f));
FireFragmentGivenTarget( ubOwner, dStartX, dStartY, dStartZ + z_offset, dEndX, dEndY, dEndZ + z_offset, usItem );
}
}
// Flugente: shoot a gun without anyone operating it (used for makeshift traps wih guns)
void FireFragmentsTrapGun( SOLDIERTYPE* pThrower, INT32 gridno, INT16 sZ, OBJECTTYPE* pObj, UINT8 ubDirection )
{
if ( !pObj )
return;
if ( !pThrower )
return;
// at the moment always only 1 bullet
UINT16 usNumFragments = 1;
UINT16 ubFragRange = GunRange( pObj, NULL );
// deviation arcs. A gun fired by tripping a wire isn't exactly precise
INT16 horizontalarc = 5;
INT16 verticalarc = 0;
INT16 sX = CenterX(gridno);
INT16 sY = CenterY(gridno);
AssertMsg( ubFragRange > 0 , "Fragmentation data lacks range property!" );
// on some maps, the floor height is not 0 (example is Drassen D13). We have to account for that with an offset
UINT32 z_offset = CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[gridno].sHeight );
for (UINT16 x = 0; x < usNumFragments; ++x)
{
FLOAT dRandomX = 0;
FLOAT dRandomY = 0;
FLOAT dRandomZ = 0;
// Flugente: if item is a directional explosive, determine in what direction the frags should fly
INT16 degree = (45 + ubDirection * 45) % 360; // modulo 360 to prevent nonsense from nonsensical input
//INT16 horizontalarc = horizontalarc % 360; // modulo 360 to prevent nonsense from nonsensical input
INT16 halfhorizontalarc = (INT16)(horizontalarc / 2);
INT16 sLowHorizontalD = (360 + degree - halfhorizontalarc) % 360;
INT16 dRandomDegreeH = (sLowHorizontalD + Random(horizontalarc) ) % 360;
// transform the degree into our coordinates
if ( dRandomDegreeH < 90 )
{
dRandomX = (FLOAT)dRandomDegreeH / 45.0f;
dRandomY = 0.0;
}
else if ( dRandomDegreeH < 180 )
{
dRandomX = (FLOAT)2.0;
dRandomY = (FLOAT)(dRandomDegreeH - 90 ) / 45.0f;
}
else if ( dRandomDegreeH < 270 )
{
dRandomX = (FLOAT)(270 - dRandomDegreeH) / 45.0f;
dRandomY = (FLOAT)2.0;
}
else
{
dRandomX = 0.0;
dRandomY = (FLOAT)(360 - dRandomDegreeH) / 45.0f;
}
// X and Y now need to be distributed, at the moment they are on a circle
// project into [-1.0, 1.0]
dRandomX -= 1.0f;
dRandomY -= 1.0f;
// vertical stuff
//INT16 verticalarc = verticalarc % 180; // modulo 180 to prevent nonsense from nonsensical input
INT16 halfverticalarc = (INT16)(verticalarc / 2);
INT16 sLowVerticalD = (90 - halfverticalarc) % 180;
INT16 dRandomDegreeV = sLowVerticalD + Random(verticalarc);
dRandomZ = ((FLOAT)(dRandomDegreeV) / 90.0f) - 1.0f;
FLOAT dDeltaX = (dRandomX * ubFragRange);
FLOAT dDeltaY = (dRandomY * ubFragRange);
FLOAT dDeltaZ = ((dRandomZ * 25.6f) * 50 );
// silversurfer: changed that from 10 to 1 because ubFragRange is already in tiles * 10. A Claymore for example has ubFragRange = 100. Multiplying again by 10 means 100 tiles range!
FLOAT dRangeMultiplier = 1; // Arbitrary, but gives good results.
FLOAT dEndX = (FLOAT)(sX + (dDeltaX * dRangeMultiplier));
FLOAT dEndY = (FLOAT)(sY + (dDeltaY * dRangeMultiplier));
FLOAT dEndZ = (FLOAT)(sZ + (dDeltaZ * dRangeMultiplier));
FireBulletGivenTargetTrapOnly( pThrower, pObj, gridno, 150 + z_offset, dEndX, dEndY, dEndZ + z_offset, 100 );
}
}
#ifdef JA2UB
//-- UB
void HavePersonAtGridnoStop( UINT32 sGridNo )
{
UINT8 ubID;
//Sewe if there is a person at the gridno
ubID = WhoIsThere2( sGridNo, 0 );
//is it a valid person
if ( (ubID != NOBODY) && (MercPtrs[ ubID ]->bTeam == gbPlayerNum) )
{
SOLDIERTYPE *pSoldier = MercPtrs[ ubID ];
//Stop the merc
pSoldier->EVENT_StopMerc( pSoldier->sGridNo, pSoldier->ubDirection );
}
}
//JA25 UB
BOOLEAN ShouldThePlayerStopWhenWalkingOnBiggensActionItem( UINT8 ubRecordNum )
{
SOLDIERTYPE *pSoldier=NULL;
pSoldier = FindSoldierByProfileID( BIGGENS_UB, TRUE ); //BIGGENS
//if biggens hasnt said the quote before, or is on the players team
if( HasNpcSaidQuoteBefore( BIGGENS_UB, ubRecordNum ) || ( pSoldier != NULL || gMercProfiles[ BIGGENS_UB ].bLife <= 0 ) ) //BIGGENS
{
return( FALSE );
}
else
{
return( TRUE );
}
}
// This function checks if we should replace the fan graphic
BOOLEAN IsFanGraphicInSectorAtThisGridNo( UINT32 sGridNo )
{
// First check current sector...... J13-0
if( gWorldSectorX == gGameUBOptions.SectorFanX && gWorldSectorY == gGameUBOptions.SectorFanY && gbWorldSectorZ == gGameUBOptions.SectorFanZ )
{
//if this is the right gridno
/*if( sGridNo == 10978 ||
sGridNo == 10979 ||
sGridNo == 10980 ||
sGridNo == 10818 ||
sGridNo == 10819 ||
sGridNo == 10820 ||
sGridNo == 10658 ||
sGridNo == 10659 ||
sGridNo == 10660 )
*/
if( sGridNo == gGameUBOptions.FanGridNo[0] ||
sGridNo == gGameUBOptions.FanGridNo[1] ||
sGridNo == gGameUBOptions.FanGridNo[2] ||
sGridNo == gGameUBOptions.FanGridNo[3] ||
sGridNo == gGameUBOptions.FanGridNo[4] ||
sGridNo == gGameUBOptions.FanGridNo[5] ||
sGridNo == gGameUBOptions.FanGridNo[6] ||
sGridNo == gGameUBOptions.FanGridNo[7] ||
sGridNo == gGameUBOptions.FanGridNo[8] )
{
return( TRUE );
}
}
return( FALSE );
}
void HandleDestructionOfPowerGenFan()
{
UINT8 ubShadeLevel=0;
//if we have already destroyed the fan
if( gJa25SaveStruct.ubHowPlayerGotThroughFan == PG__PLAYER_BLEW_UP_FAN_TO_GET_THROUGH )
{
//leave
return;
}
//if we have already been in here
if( gJa25SaveStruct.ubStateOfFanInPowerGenSector == PGF__BLOWN_UP )
{
return;
}
//Remeber that the player blew up the fan
gJa25SaveStruct.ubStateOfFanInPowerGenSector = PGF__BLOWN_UP;
//Remeber how the player got through
HandleHowPlayerGotThroughFan();
//Since the player is making LOTS of noise, add more enemies to the tunnel sector
// AddEnemiesToJa25TunnelMaps();
if ( gGameUBOptions.HandleAddingEnemiesToTunnelMaps == TRUE )
{
HandleAddingEnemiesToTunnelMaps();
}
//Make sure to apply these changes to the map
ApplyMapChangesToMapTempFile( TRUE );
//Add an exit grid to the map
AddExitGridForFanToPowerGenSector();
//done with the changes
ApplyMapChangesToMapTempFile( FALSE );
//Stop the fan sound
HandleRemovingPowerGenFanSound();
//
// Have a qualified merc say a quote
//
//Get a random qualified merc to say the quote
INT16 bID = RandomSoldierIdFromNewMercsOnPlayerTeam();
if( bID != -1 )
{
DelayedMercQuote( Menptr[ bID ].ubProfile, QUOTE_ACCEPT_CONTRACT_RENEWAL, GetWorldTotalSeconds() + 2 );
}
}
void HandleExplosionsInTunnelSector( UINT32 sGridNo )
{
//if this isnt the tunnel sectors J14-1 and K14-1
if( !( gWorldSectorX == gGameUBOptions.ExitForFanToPowerGenSectorX && ( gWorldSectorY == gGameUBOptions.ExitForFanToPowerGenSectorY || gWorldSectorY == gGameUBOptions.SectorOpenGateInTunnelY ) && gbWorldSectorZ == 1 ) )
{
//get the fuck out...
return;
}
//Since the enemy will hear explosions in the tunnel, remember the player made a noise
gJa25SaveStruct.uiJa25GeneralFlags |= JA_GF__DID_PLAYER_MAKE_SOUND_GOING_THROUGH_TUNNEL_GATE;
}
void HandleSeeingFortifiedDoor( UINT32 sGridNo )
{
//if this isnt the First level of the complex K15-1
if( !( gWorldSectorX == gGameUBOptions.SectorDoorInTunnelX && gWorldSectorY == gGameUBOptions.SectorDoorInTunnelY && gbWorldSectorZ == gGameUBOptions.SectorDoorInTunnelZ ) )
{
//get the fuck out...
return;
}
//if the player has already seen it
if( gJa25SaveStruct.uiJa25GeneralFlags & JA_GF__PLAYER_HAS_SEEN_FORTIFIED_DOOR )
{
//get out
return;
}
//Remeber that we have said the quote
gJa25SaveStruct.uiJa25GeneralFlags |= JA_GF__PLAYER_HAS_SEEN_FORTIFIED_DOOR;
//find out whos is the one walking across the trap
INT16 sID = WhoIsThere2( sGridNo, 0 );
if( sID != NOBODY && IsSoldierQualifiedMerc( &Menptr[ sID ] ) )
{
}
else
{
//Get a random merc to say quote
sID = RandomSoldierIdFromNewMercsOnPlayerTeam();
}
if( sID != -1 )
{
//say the quote
TacticalCharacterDialogue( &Menptr[ sID ], QUOTE_LENGTH_OF_CONTRACT );
}
}
void HandleSwitchToOpenFortifiedDoor( UINT32 sGridNo )
{
//if the door is already opened
if( gJa25SaveStruct.ubStatusOfFortifiedDoor == FD__OPEN )
{
return;
}
//remeber that the switch to open the forified door on level 1, has been pulled
gJa25SaveStruct.ubStatusOfFortifiedDoor = FD__OPEN;
INT16 bID = RandomSoldierIdFromNewMercsOnPlayerTeam();
if( bID != -1 )
{
TacticalCharacterDialogue( &Menptr[ bID ], QUOTE_COMMENT_BEFORE_HANG_UP );
}
}
void HandleSeeingPowerGenFan( UINT32 sGridNo )
{
// INT8 bID;
UINT8 ubPerson;
BOOLEAN fFanIsStopped;
BOOLEAN fFanHasBeenStopped;
SOLDIERTYPE *pSoldier;
SOLDIERTYPE *pOtherSoldier;
INT32 cnt;
BOOLEAN fSaidQuote=FALSE;
//if the fan has already been seen
if( IsJa25GeneralFlagSet( JA_GF__PLAYER_SEEN_FAN_BEFORE ) )
{
//get out
return;
}
fFanIsStopped = ( gJa25SaveStruct.ubStateOfFanInPowerGenSector == PGF__STOPPED );
fFanHasBeenStopped = IsJa25GeneralFlagSet( JA_GF__POWER_GEN_FAN_HAS_BEEN_STOPPED );
//Get the person who is at the gridno
ubPerson = WhoIsThere2( sGridNo, 0 );
if( ubPerson != NOBODY )
{
pSoldier = &Menptr[ ubPerson ];
//if the fan is stopped And is this merc is a qualified merc but Not a power gen fan qualified merc?
if( IsSoldierQualifiedMerc( pSoldier ) && fFanIsStopped )
{
//Have the merc say the quote
TacticalCharacterDialogue( pSoldier, QUOTE_HATED_2_ON_TEAM_WONT_RENEW );
fSaidQuote = TRUE;
}
else if( IsSoldierQualifiedMercForSeeingPowerGenFan( pSoldier ) )
{
//Have the merc say the quote
TacticalCharacterDialogue( pSoldier, QUOTE_HATED_2_ON_TEAM_WONT_RENEW );
fSaidQuote = TRUE;
}
else
{
//see if there is another merc that is close by to say the quote
cnt = gTacticalStatus.Team[ OUR_TEAM ].bFirstID;
for ( pOtherSoldier = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; cnt++,pOtherSoldier++)
{
//if the soldier is in the sector
if( pOtherSoldier->bActive && pOtherSoldier->bInSector && ( pOtherSoldier->stats.bLife >= CONSCIOUSNESS ) )
{
INT16 sDistanceAway;
// if the soldier isnt that far away AND he is qualified merc
sDistanceAway = PythSpacesAway( pSoldier->sGridNo, pOtherSoldier->sGridNo );
if( sDistanceAway <= 5 && ( !InARoom( pOtherSoldier->sGridNo, NULL ) || pOtherSoldier->pathing.bLevel ) &&
( ( IsSoldierQualifiedMerc( pOtherSoldier ) && fFanIsStopped ) ||
( IsSoldierQualifiedMercForSeeingPowerGenFan( pOtherSoldier ) ) ) )
{
//Have the merc say the quote
TacticalCharacterDialogue( pOtherSoldier, QUOTE_HATED_2_ON_TEAM_WONT_RENEW );
fSaidQuote = TRUE;
break;
}
}
}
}
}
//if the quote was said, dont say it again
if( fSaidQuote )
{
//remeber that the fan has been seen
SetJa25GeneralFlag( JA_GF__PLAYER_SEEN_FAN_BEFORE );
}
}
#endif
void HandleBuddyExplosions(UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, INT32 sGridNo, UINT16 usItem, BOOLEAN fLocate, INT8 bLevel, UINT8 ubDirection )
{
// Flugente: Items can have secondary explosions
if ( Item[usItem].usBuddyItem )
{
if ( Item[Item[usItem].usBuddyItem].flare )
{
if( !sZ || !FindBuilding(sGridNo) )
{
// Add a light effect...
NewLightEffect( sGridNo, (UINT8)Explosive[Item[Item[usItem].usBuddyItem].ubClassIndex].ubDuration , (UINT8)Explosive[Item[Item[usItem].usBuddyItem].ubClassIndex].ubStartRadius );
}
}
else if ( Item[Item[usItem].usBuddyItem ].usItemClass & (IC_GRENADE|IC_BOMB) )
{
IgniteExplosion( ubOwner, sX, sY, sZ, sGridNo, Item[usItem].usBuddyItem, bLevel, ubDirection );
}
}
}
// sevenfm: handle explosive items from attachments
BOOLEAN HandleAttachedExplosions(UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, INT32 sGridNo, UINT16 usItem, BOOLEAN fLocate, INT8 bLevel, UINT8 ubDirection, OBJECTTYPE * pObj)
{
BOOLEAN binderFound = FALSE;
BOOLEAN detonator = FALSE;
BOOLEAN fAttFound = FALSE;
attachmentList::iterator iterend;
attachmentList::iterator iter;
UINT8 direction;
if(pObj==NULL)
return FALSE;
binderFound = FindBinderAttachment ( pObj );
detonator = CheckExplosiveTypeAsDetonator( Explosive[ Item[ usItem ].ubClassIndex ].ubType );
// search for attached explosives
iterend = (*pObj)[0]->attachments.end();
for (iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
{
if ( iter->exists() && Item[iter->usItem].usItemClass & (IC_GRENADE|IC_BOMB) )
{
// no need for binder if both item and attachment are tripwire-activated
if( ( Item[pObj->usItem].tripwireactivation && Item[iter->usItem].tripwireactivation ) ||
( binderFound && detonator && Explosive[Item[iter->usItem].ubClassIndex].ubVolatility > 0 ) )
{
if(Item[iter->usItem].directional && ubDirection == DIRECTION_IRRELEVANT)
direction=Random(8);
else
direction=ubDirection;
if( Item[iter->usItem].uiIndex == TRIP_KLAXON )
{
PlayJA2Sample( KLAXON_ALARM, RATE_11025, SoundVolume( MIDVOLUME, sGridNo ), 5, SoundDir( sGridNo ) );
// CallAvailableEnemiesTo( sGridNo );
MakeNoise( NOBODY, sGridNo, bLevel, gpWorldLevelData[ sGridNo ].ubTerrainID, (UINT8)Explosive[ Item[iter->usItem].ubClassIndex ].ubVolume, NOISE_EXPLOSION );
} else if( Item[iter->usItem].uiIndex == TRIP_FLARE )
{
NewLightEffect( sGridNo, (UINT8)Explosive[ Item[iter->usItem].ubClassIndex ].ubDuration, (UINT8)Explosive[iter->usItem].ubStartRadius );
} else
{
IgniteExplosion( ubOwner, sX, sY, sZ, sGridNo, Item[iter->usItem].uiIndex, bLevel, direction , NULL );
}
fAttFound = TRUE;
}
}
if ( binderFound && detonator && gGameExternalOptions.bAllowSpecialExplosiveAttachments && iter->exists() && Item[iter->usItem].usItemClass & IC_MISC )
{
if(Item[iter->usItem].gascan)
{
IgniteExplosion( ubOwner, sX, sY, sZ, sGridNo, GAS_EXPLOSION, bLevel, DIRECTION_IRRELEVANT , NULL );
fAttFound = TRUE;
}
if ( Item[iter->usItem].alcohol > 0.0f )
{
IgniteExplosion( ubOwner, sX, sY, sZ, sGridNo, MOLOTOV_EXPLOSION, bLevel, DIRECTION_IRRELEVANT , NULL );
fAttFound = TRUE;
}
if(Item[iter->usItem].marbles)
{
IgniteExplosion( ubOwner, sX, sY, sZ, sGridNo, FRAG_EXPLOSION, bLevel, DIRECTION_IRRELEVANT , NULL );
fAttFound = TRUE;
}
}
}
return fAttFound;
}
void CheckForBuriedBombsAndRemoveFlags( INT32 sGridNo, INT8 bLevel )
{
if ( FindWorldItemForBuriedBombInGridNo(sGridNo, bLevel) == -1 )
{
// make sure no one thinks there is a bomb here any more!
if ( gpWorldLevelData[sGridNo].uiFlags & MAPELEMENT_PLAYER_MINE_PRESENT )
{
RemoveBlueFlag( sGridNo, bLevel );
}
gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_ENEMY_MINE_PRESENT);
}
}
// sevenfm: calculate total average volatility of item+attachments
UINT16 CalcTotalVolatility(OBJECTTYPE * pObj)
{
BOOLEAN binderFound = FALSE;
BOOLEAN detonator = FALSE;
attachmentList::iterator iterend;
attachmentList::iterator iter;
UINT16 totalVolatility;
UINT8 num;
UINT16 usItem;
UINT16 classIndex;
if(pObj==NULL)
return 0;
usItem = pObj->usItem;
classIndex = Item[ usItem ].ubClassIndex;
totalVolatility = Explosive[ classIndex ].ubVolatility;
num = 1;
binderFound = FindBinderAttachment ( pObj );
detonator = CheckExplosiveTypeAsDetonator( Explosive[ classIndex ].ubType );
// search for attached explosives
iterend = (*pObj)[0]->attachments.end();
for (iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
{
if ( iter->exists() && Item[iter->usItem].usItemClass & (IC_GRENADE|IC_BOMB) )
{
// no need for binder if both item and attachment are tripwire-activated
if( ( Item[usItem].tripwireactivation && Item[iter->usItem].tripwireactivation ) ||
( binderFound && detonator && Explosive[Item[iter->usItem].ubClassIndex].ubVolatility > 0 ) )
{
totalVolatility += Explosive[Item[iter->usItem].ubClassIndex].ubVolatility;
num++;
}
}
}
return totalVolatility / num;
}
BOOLEAN FindBinderAttachment (OBJECTTYPE * pObj)
{
attachmentList::iterator iterend;
attachmentList::iterator iter;
// check all attachments, search for ELASTIC or DUCT_TAPE;
iterend = (*pObj)[0]->attachments.end();
for (iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
{
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_MISC )
{
if(Item[iter->usItem].uiIndex == ELASTIC || Item[iter->usItem].uiIndex == DUCT_TAPE )
{
return TRUE;
}
}
}
return FALSE;
}
BOOLEAN CheckExplosiveTypeAsDetonator(UINT16 ubType)
{
// attached explosives are allowed only for EXPLOSV_NORMAL, EXPLOSV_STUN and EXPLOSV_FLASHBANG types
return ( ubType == EXPLOSV_NORMAL || ubType == EXPLOSV_STUN || ubType == EXPLOSV_FLASHBANG );
}
typedef std::pair<INT32, UINT8> gridnoarmourpair;
typedef std::vector< gridnoarmourpair > gridnoarmourvector;
typedef std::map<UINT32, gridnoarmourvector> gridnoarmournetworkmap;
void SoldierDropThroughRoof( SOLDIERTYPE* pSoldier, INT32 sGridNo )
{
if ( !pSoldier || TileIsOutOfBounds( sGridNo ) )
return;
pSoldier->SetSoldierHeight( 0.0 );
TeleportSoldier( pSoldier, sGridNo, TRUE );
pSoldier->EVENT_StopMerc( pSoldier->sGridNo, pSoldier->ubDirection );
// if we play the animation of falling roof tiles over this, it will look like the merc fell really painful (there's even blood)
SoldierCollapse( pSoldier );
// take damage
UINT32 damage = 15 + Random( 5 ) + Random( 23 );
pSoldier->SoldierTakeDamage( ANIM_CROUCH, damage, damage * 100, TAKE_DAMAGE_FALLROOF, NOBODY, NOWHERE, 0, TRUE );
}
gridnoarmourvector GetConnectedRoofGridnoArmours( INT32 sGridNo, UINT8& arBestArmour )
{
gridnoarmourvector vec;
// this shouldn't even happen
if ( TileIsOutOfBounds( sGridNo ) || !IsRoofPresentAtGridNo( sGridNo ) )
return vec;
UINT8 armour = 0;
STRUCTURE* pStruct = FindStructure( sGridNo, (STRUCTURE_WALL) );
if ( pStruct )
armour = gubMaterialArmour[pStruct->pDBStructureRef->pDBStructure->ubArmour];
arBestArmour = armour;
vec.push_back( std::make_pair( sGridNo, armour ) );
// starting from sGridNo, we inspect neighbouring gridnos to detect tiles of the same roof, and detect wether there is a wall below them
// if there is indeed a wall, note its armour level
// if we already intersected a roof by deleting roof tiles, this function will only detect the connected roof part
UINT32 cnt = 0;
while ( cnt < vec.size( ) )
{
INT32 currentgridno = vec[cnt++].first;
// determine all neighbouring gridnos. If they have a roof, add them to the list
for ( int i = 0; i < 4; ++i )
{
INT32 nextgridno = currentgridno + 1;
if ( i == 1 )
nextgridno = currentgridno - 1;
else if ( i == 2 )
nextgridno = currentgridno + WORLD_COLS;
if ( i == 3 )
nextgridno = currentgridno - WORLD_COLS;
if ( !TileIsOutOfBounds( nextgridno ) && IsRoofPresentAtGridNo( nextgridno ) )
{
armour = 0;
pStruct = FindStructure( nextgridno, (STRUCTURE_WALL) );
if ( pStruct )
armour = gubMaterialArmour[pStruct->pDBStructureRef->pDBStructure->ubArmour];
gridnoarmourpair pair( nextgridno, armour );
if ( std::find( vec.begin(), vec.end(), pair ) == vec.end() )
{
vec.push_back( pair );
if ( arBestArmour < armour )
arBestArmour = armour;
}
}
}
}
return vec;
}
// after roof tiles have collapsed, the connected roof tile network can degenerate into several unconnected networks
// we now collect all these networks
gridnoarmournetworkmap GetConnectedRoofNetworks( gridnoarmourvector& arNetwork )
{
gridnoarmournetworkmap map;
// we need a copy of the thing
gridnoarmourvector networkcpy = arNetwork;
UINT16 networkcounter = 0;
UINT8 bestarmour = 0;
// at first, each nod is its own network. Ignore nodes that have already been destroyed (those that don't have a roof anymore)
for ( gridnoarmourvector::iterator it = networkcpy.begin( ); it != networkcpy.end( ); ++it )
{
gridnoarmourpair pair = (*it);
if ( IsRoofPresentAtGridNo( pair.first ) )
{
gridnoarmourvector vect = GetConnectedRoofGridnoArmours( pair.first, bestarmour );
// delete all nodes further down in networkcpy that are also present in vect
for ( gridnoarmourvector::iterator tmpit = vect.begin( ); tmpit != vect.end( ); ++tmpit )
{
networkcpy.erase( std::remove( it + 1, networkcpy.end( ), (*tmpit) ), networkcpy.end( ) );
}
map[networkcounter++] = vect;
if ( it == networkcpy.end( ) )
break;
}
}
return map;
}
// for a given network, return the same network with updated armour values: wall armour partially extends to neighbouring nodes
gridnoarmourvector GetArmourSharedRoofNetwork( gridnoarmourvector& arNetwork )
{
// we return a new network
gridnoarmourvector vec = arNetwork;
BOOLEAN fChangeApplied = TRUE;
while ( fChangeApplied )
{
fChangeApplied = FALSE;
gridnoarmourvector::iterator itend = vec.end( );
for ( gridnoarmourvector::iterator it1 = vec.begin( ); it1 != itend; ++it1 )
{
gridnoarmourpair& pair1 = (*it1);
if ( !IsRoofPresentAtGridNo( pair1.first ) )
continue;
// loop over all other nodes
for ( gridnoarmourvector::iterator it2 = vec.begin( ); it2 != itend; ++it2 )
{
gridnoarmourpair& pair2 = (*it2);
if ( !IsRoofPresentAtGridNo( pair2.first ) )
continue;
if ( PythSpacesAway( pair1.first, pair2.first ) == 1 )
{
UINT8 armour1 = pair1.second;
UINT8 armour2 = pair2.second;
UINT8 sharedarmour1 = max( armour1, armour2 * WALLSTRENGTH_NEIGHBOURING_PER_TILE );
UINT8 sharedarmour2 = max( armour1 * WALLSTRENGTH_NEIGHBOURING_PER_TILE, armour2 );
if ( armour1 < sharedarmour1 )
{
pair1.second = sharedarmour1;
fChangeApplied = TRUE;
}
if ( armour2 < sharedarmour2 )
{
pair2.second = sharedarmour2;
fChangeApplied = TRUE;
}
}
}
}
}
return vec;
}
// handle destroying if a single roof tile
void RoofDestruction( INT32 sGridNo, BOOLEAN fWithExplosion )
{
SOLDIERTYPE* pSoldier = NULL;
if ( TileIsOutOfBounds( sGridNo ) || !IsRoofPresentAtGridNo( sGridNo ) )
return;
// any items on this roof drop to the floor below
// if there is already a structure here, we need to drop the gear next to it...
INT32 geardropoffgridno = sGridNo;
if ( !IsLocationSittableExcludingPeople( geardropoffgridno, FALSE ) )
{
for ( INT32 i = -1; i <= 1; ++i )
{
for ( INT32 j = -1; j <= 1; ++j )
{
INT32 tmpgridno = sGridNo + i + j * WORLD_COLS;
// if this gridno has place for gear, and is in the same room as the original sGridNo (or both are not in a room)
if ( IsLocationSittableExcludingPeople( tmpgridno, FALSE ) && gusWorldRoomInfo[sGridNo] == gusWorldRoomInfo[tmpgridno] )
{
geardropoffgridno = tmpgridno;
break;
}
}
}
}
MoveItemPools( sGridNo, geardropoffgridno, 1, 0 );
// if there is anybody at the floor level, damage them with the debris
// we have to do this BEFORE people drop down. It would otherwise be possible for a falling person to avoid this damage, depending on order of collapsing and possible gridno-shifts.
UINT16 ubId = WhoIsThere2( sGridNo, 0 );
if ( ubId != NOBODY )
{
pSoldier = MercPtrs[ubId];
pSoldier->EVENT_StopMerc( pSoldier->sGridNo, pSoldier->ubDirection );
// if we play the animation of falling roof tiles over this, it will look like the merc fell really painful (there's even blood)
SoldierCollapse( pSoldier );
// take damage
UINT32 damage = 10 + Random( 3 ) + Random( 10 );
pSoldier->SoldierTakeDamage( ANIM_CROUCH, damage, damage * 100, TAKE_DAMAGE_FALLROOF, NOBODY, NOWHERE, 0, TRUE );
}
// if there is a person here, drop them to the ground...
ubId = WhoIsThere2( sGridNo, 1 );
if ( ubId != NOBODY )
{
pSoldier = MercPtrs[ubId];
INT32 soldierdropoffgridno = sGridNo;
if ( !IsLocationSittable( soldierdropoffgridno, FALSE ) )
{
for ( INT32 i = -1; i <= 1; ++i )
{
for ( INT32 j = -1; j <= 1; ++j )
{
INT32 tmpgridno = sGridNo + i + j * WORLD_COLS;
// if this gridno has place for gear, and is in the same room as the original sGridNo (or both are not in a room)
if ( IsLocationSittable( tmpgridno, FALSE ) && gusWorldRoomInfo[sGridNo] == gusWorldRoomInfo[tmpgridno] )
{
soldierdropoffgridno = tmpgridno;
break;
}
}
}
}
SoldierDropThroughRoof( pSoldier, soldierdropoffgridno );
}
// there can be non-roof structures on roofs. In order to collapse those, check for all structures who are above a certain height, and destroy them
STRUCTURE* pStruct = GetTallestStructureOnGridno( sGridNo, 1 );
while ( pStruct )
{
LEVELNODE* pNode = FindLevelNodeBasedOnStructure( pStruct->sGridNo, pStruct );
if ( pNode )
{
if ( RemoveOnRoofStruct( sGridNo, pNode->usIndex ) )
pStruct = GetTallestStructureOnGridno( sGridNo, 1 );
else
break;
}
else
break;
}
// very important: if applying changes to map files is turned on, adding corpses will corrupt the map (only noticeable if you reload it)
// for this reason we turn this off and on again
ApplyMapChangesToMapTempFile( FALSE );
// if there is a corpse here, drop that down as well
ShiftCorpse( sGridNo , 1);
ApplyMapChangesToMapTempFile( TRUE );
// play an animation of falling roof tiles
if ( fWithExplosion )
{
// if we merely collected the tiles that collapse and then call them all together, it would be possible to have animations for multi-tile collapses
static UINT16 usRoofCollapseExplosionIndex = 1727;
if ( HasItemFlag( usRoofCollapseExplosionIndex, ROOF_COLLAPSE_ITEM ) || GetFirstItemWithFlag( &usRoofCollapseExplosionIndex, ROOF_COLLAPSE_ITEM ) )
{
InternalIgniteExplosion( NOBODY, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, usRoofCollapseExplosionIndex, FALSE, 0 );
}
if ( Chance( 15 ) )
{
static UINT16 debrissmokeitem = GetFirstExplosiveOfType( EXPLOSV_SMOKE_DEBRIS );
if ( debrissmokeitem )
{
InternalIgniteExplosion( NOBODY, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, debrissmokeitem, FALSE, 0 );
}
}
}
RemoveAllRoofsOfTypeRangeAdjustSaveFile( sGridNo, FIRSTTEXTURE, WIREFRAMES );
}
// damage, or even destroy, a roof tile
// return true if it roof was destroyed
BOOLEAN DamageRoof( INT32 sGridNo, INT16 sDamage )
{
// only if there is significant damage done
if ( sDamage < 1 || TileIsOutOfBounds( sGridNo ) || !IsRoofPresentAtGridNo( sGridNo ) )
return FALSE;
// sadly, pStruct->ubHitPoints of roof structures is shadowed by sBaseGridNo, so we cannot use it
// pStruct->pDBStructureRef->pDBStructure->ubHitPoints is always 0 for roof tiles, so we cannot use that either
// therefore roofs have no HP pool, and we have to outright destroy them
// should that ever be corrected, comment this part back in
// I could jut write something into pStruct->pDBStructureRef->pDBStructure->ubHitPoints, but knowing the code the 0 will also be used somewhere else - if you try that, be careful!
/*STRUCTURE* pStruct = FindStructure( sGridNo, STRUCTURE_ROOF );
if ( pStruct )
{
// if damage exceeds hitpoints, destroy the thing
if ( (UINT8)sDamage >= pStruct->pDBStructureRef->pDBStructure->ubHitPoints )
{
RoofDestruction( sGridNo );
return TRUE;
}
// otherwise just damage it
else
{
pStruct->pDBStructureRef->pDBStructure->ubHitPoints = (UINT8)max( 1, pStruct->ubHitPoints - sDamage );
}
}
return FALSE;*/
// just destroy the thing
RoofDestruction( sGridNo );
return TRUE;
}
void HandleRoofDestruction( INT32 sGridNo, INT16 sDamage )
{
// only if there is significant damage done
if ( sDamage < 1 || !gGameExternalOptions.fRoofCollapse || TileIsOutOfBounds( sGridNo ) || !IsRoofPresentAtGridNo( sGridNo ) )
return;
UINT8 bestarmour = 0;
gridnoarmourvector floorarmourvector = GetConnectedRoofGridnoArmours( sGridNo, bestarmour );
// armour above 127 is deemed indestructable
if ( bestarmour >= 127 )
return;
// we can't do anything if damage is lower than armour in the first place
if ( sDamage <= bestarmour )
return;
ApplyMapChangesToMapTempFile( TRUE );
for ( gridnoarmourvector::iterator it = floorarmourvector.begin( ), itend = floorarmourvector.end(); it != itend; ++it )
{
// for formula reasons, distance is at least 1
INT16 distance = max( 1, PythSpacesAway( sGridNo, ( *it ).first ) );
// only remove tile if enough damage has been done
// it might be necessary to tweak the damage formula here
if ( sDamage > distance * bestarmour )
{
if ( DamageRoof( ( *it ).first, sDamage - distance * bestarmour ) )
(*it).second = 0;
}
}
// Nodes might have been removed, and the roof might have degenerated into several networks
gridnoarmournetworkmap roofnetworkmap = GetConnectedRoofNetworks( floorarmourvector );
// for each remaining node, determine the distance to the closest node with a wall-connection inside the remaining network. If the distance is high enough, the roof will come down
for ( gridnoarmournetworkmap::iterator roofnetworkit = roofnetworkmap.begin( ), roofnetworkitend = roofnetworkmap.end(); roofnetworkit != roofnetworkitend; ++roofnetworkit )
{
gridnoarmourvector roofnetwork = (*roofnetworkit).second;
// sadly the calculating time is somewhat high if there are many nodes connected to the roof, like Alma prison.
// therefore this part is commented out. Can be commented in once the speed issues have been resolved
// properly done, this would simulate the 'structural integrity' of the building
// for each remaining node, determine the distance to the closest node with a wall-connection inside the remaining network. If the distance is high enough, the roof will come down
if ( roofnetwork.size( ) < 200 )
{
gridnoarmourvector roofarmoursharednetwork = GetArmourSharedRoofNetwork( roofnetwork );
for ( gridnoarmourvector::iterator it = roofarmoursharednetwork.begin( ), itend = roofarmoursharednetwork.end(); it != itend; ++it )
{
gridnoarmourpair pair = (*it);
// if it does not have a roof, ignore
if ( !IsRoofPresentAtGridNo( pair.first ) )
{
pair.second = 0;
continue;
}
// destroy non-supported roofs
if ( pair.second < 1 )
{
if ( DamageRoof( pair.first, 255 ) )
pair.second = 0;
}
}
}
else
{
// for now, determine the best armour for each remaining network, and collapse it if there is no armour - and thus no wall connection - left
// this means that if ANY armour is found, we won't collapse this network, so we can stop the search for better armour
bestarmour = 0;
for ( gridnoarmourvector::iterator it = roofnetwork.begin(), itend = roofnetwork.end(); it != itend; ++it )
{
if ( ( *it ).second > 0 )
{
bestarmour = ( *it ).second;
break;
}
}
// if a network has no wall connection at all, collapse the entire thing
if ( bestarmour < 1 )
{
for ( gridnoarmourvector::iterator it = roofnetwork.begin( ), itend = roofnetwork.end(); it != itend; ++it )
{
if ( DamageRoof( (*it).first, 255 ) )
(*it).second = 0;
}
}
}
}
ApplyMapChangesToMapTempFile( FALSE );
// FOR THE NEXT RENDER LOOP, RE-EVALUATE REDUNDENT TILES
InvalidateWorldRedundency( );
SetRenderFlags( RENDER_FLAG_FULL );
}