#include "builddefines.h" #include "WorldDat.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, SoldierID 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( SoldierID 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( UINT16 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; SoldierID gubPersonToSetOffExplosions = NOBODY; INT32 gsTempActionGridNo = NOWHERE; extern SoldierID 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, SoldierID 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( SoldierID 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.i, 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 = ubOwner->GetUsedWeapon( &( ubOwner->inv[ 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 ( 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( 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( SoldierID 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; SoldierID 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; SoldierID 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, SoldierID 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.i , 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 ConvertGridNoToCenterCellXY(sGridNo, &sX, &sY); // 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 ) { INT16 sX, sY; ConvertGridNoToCenterCellXY(sBaseGridNo, &sX, &sY); InternalIgniteExplosion( ubOwner, sX, sY, 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, SoldierID 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.i , 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 ); } { INT16 sX, sY; ConvertGridNoToCenterCellXY(sNewGridNo2, &sX, &sY); InternalIgniteExplosion( ubOwner, sX, sY, 0, sNewGridNo2, RDX, FALSE, bLevel ); } fToBreak = TRUE; } } } } } } if ( fToBreak ) { break; } } } if ( pBaseStructure ) { MemFree( ppTile ); } pCurrent = pNextCurrent; } } BOOLEAN DamageSoldierFromBlast( SoldierID ubPerson, SoldierID ubOwner, INT32 sBombGridNo, INT16 sWoundAmt, INT16 sBreathAmt, UINT32 uiDist, UINT16 usItem, INT16 sSubsequent, BOOL fFromRemoteClient ) { // OJW - 20091028 if (is_networked && is_client) { SOLDIERTYPE* pSoldier = 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.i, ubOwner.i , 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 = 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 && gGameOptions.fNewTraitSystem) { // Demolitions damage bonus with bombs and mines if ( HAS_SKILL_TRAIT( ubOwner, DEMOLITIONS_NT ) && Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL && Item[usItem].usItemClass == IC_BOMB && (!ItemIsAttachment(usItem) || ItemIsMine(usItem))) { sNewWoundAmt = (INT16)(sNewWoundAmt * (100 + gSkillTraitValues.ubDEDamageOfBombsAndMines) / 100.0f + 0.5f); } // Heavy Weapons trait bonus damage to tanks if ( HAS_SKILL_TRAIT( 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( 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( ubOwner, HEAVY_WEAPONS_NT ) && Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL && ((Item[usItem].usItemClass == IC_BOMB && ItemIsAttachment(usItem) && !ItemIsMine(usItem) ) || // mortar shells (Item[usItem].usItemClass == IC_GRENADE && (ItemIsGLgrenade(usItem) || ItemIsElectronic(usItem)) ) || // rockets for rocketlaunchers (I haven't found any other way) (Item[usItem].usItemClass == IC_LAUNCHER ) || ItemIsRocketLauncher(usItem) || ItemIsSingleShotRocketLauncher(usItem) ) ) { sNewWoundAmt = (INT16)(sNewWoundAmt * (100 + gSkillTraitValues.ubHWDamageBonusPercentForHW * NUM_SKILL_TRAITS( 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 ) || ItemIsGLgrenade(usItem) ) && !ItemIsElectronic(usItem)) 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 && ubOwner->bTeam == gbPlayerNum && pSoldier->bTeam != gbPlayerNum ) { ProcessImplicationsOfPCAttack( ubOwner, &pSoldier, REASON_EXPLOSION ); } return( TRUE ); } BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, INT16 sSubsequent, BOOLEAN fRecompileMovementCosts, INT16 sWoundAmt, INT16 sBreathAmt, SoldierID 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.i , pExplosive->ubType , sSubsequent , fRecompileMovementCosts , sWoundAmt , sBreathAmt , ubOwner.i , 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 && ubOwner->bTeam == gbPlayerNum && pSoldier->bTeam != gbPlayerNum ) { ProcessImplicationsOfPCAttack( ubOwner, &pSoldier, REASON_EXPLOSION ); } } return( fRecompileMovementCosts ); } BOOLEAN ExpAffect( INT32 sBombGridNo, INT32 sGridNo, UINT32 uiDist, UINT16 usItem, SoldierID 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.i , sSubsequent , (int)*pfMercHit , bLevel , iSmokeEffectID ); MPDebugMsg(tmpMPDbgString); } #endif INT16 sWoundAmt = 0,sBreathAmt = 0, /* sNewWoundAmt = 0, sNewBreathAmt = 0, */ sStructDmgAmt; SoldierID 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 = 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, SoldierID ubOwner, BOOLEAN fSubsequent, INT8 bLevel, INT32 iSmokeEffectID, BOOL fFromRemoteClient, BOOL fNewSmokeEffect ) { if (is_networked && is_client) { SOLDIERTYPE* pAttacker = 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.i , (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 UINT16 usTempRoom; SOLDIERTYPE * pSoldier; for ( SoldierID ubLoop = gTacticalStatus.Team[ CIV_TEAM ].bFirstID; ubLoop <= gTacticalStatus.Team[ CIV_TEAM ].bLastID; ++ubLoop ) { pSoldier = 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) { SoldierID soldierID = wi.soldierID; // bomb's owner if (soldierID == NOBODY) soldierID = wi.object[0]->data.misc.ubBombOwner - 2; // undo the hack if (IsOurSoldier((SOLDIERTYPE*)gubPersonToSetOffExplosions) || IsOurSoldier((SOLDIERTYPE*)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( SoldierID 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 (ItemHasTripwireActivation(usBombItem)) { // tripwire just gets activated if (ItemIsTripwire(usBombItem)) { // 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 ( (ubID->bTeam != gbPlayerNum) && ((*pObj)[0]->data.misc.ubBombOwner > 1) ) { if ( ((*pObj)[0]->data.misc.ubBombOwner - 2)->ubProfile != NO_PROFILE && ((*pObj)[0]->data.misc.ubBombOwner - 2)->bTeam == gbPlayerNum ) gMercProfiles[ ((*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, SoldierID 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( 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 || ItemIsFlare(pObj->usItem)) { // 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 ) ); SoldierID ubID = WhoIsThere2( sGridNo, ubLevel ); if ( ubID != NOBODY ) { SOLDIERTYPE* pSoldier = 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 (ItemIsTripwire(pObj->usItem)) { 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 ) { INT16 sX, sY; ConvertGridNoToCenterCellXY(sGridNo, &sX, &sY); fAttFound = HandleAttachedExplosions( ((*pObj)[0]->data.misc.ubBombOwner - 2), sX, sY, 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 && ItemIsTripwire((*pObj)[0]->data.misc.usBombItem) ) { 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 SoldierID 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! INT16 sX, sY; ConvertGridNoToCenterCellXY(sGridNo, &sX, &sY); // bomb objects only store the SIDE who placed the bomb! :-( if ( (*pObj)[0]->data.misc.ubBombOwner > 1 ) { IgniteExplosion( ((*pObj)[0]->data.misc.ubBombOwner - 2), sX, sY, 0, sGridNo, (*pObj)[0]->data.misc.usBombItem, ubLevel, (*pObj)[0]->data.ubDirection, pObj); } else { // pre-placed IgniteExplosion( NOBODY, sX, sY, 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 && !(gubPersonToSetOffExplosions->flags.uiStatusFlags & SOLDIER_PC) ) { FreeUpNPCFromPendingAction( gubPersonToSetOffExplosions ); } if (gfExplosionQueueMayHaveChangedSight) { SoldierID 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 ( ; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ++ubLoop ) { pTeamSoldier = ubLoop; 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 = 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 ( SoldierID ubID = gTacticalStatus.Team[OUR_TEAM].bFirstID; ubID <= gTacticalStatus.Team[CIV_TEAM].bLastID; ++ubID) { if (ubID != pSoldier->ubID && ubID != NOBODY && ubID->sGridNo == sSpot && ubID->bVisible == TRUE && ubID->pathing.bLevel == bLevel && gAnimControl[ubID->usAnimState].ubEndHeight == ANIM_PRONE && !Water(ubID->sGridNo, ubID->pathing.bLevel) && pSoldier->ubBodyType <= REGFEMALE && (ubID->bTeam == pSoldier->bTeam || ubID->IsUnconscious() || 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 = ((*pObj)[0]->data.misc.ubBombOwner - 2); // SANDRO - merc records - detonating explosives if ( gubPersonToSetOffExplosions->ubProfile != NO_PROFILE && gubPersonToSetOffExplosions->bTeam == gbPlayerNum ) { gMercProfiles[ 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 = ((*pObj)[0]->data.misc.ubBombOwner - 2); // SANDRO - merc records - detonating explosives if ( gubPersonToSetOffExplosions->ubProfile != NO_PROFILE && gubPersonToSetOffExplosions->bTeam == gbPlayerNum ) { gMercProfiles[ 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( SoldierID 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 ( ((*pObj)[0]->data.misc.ubBombOwner - 2)->ubProfile != NO_PROFILE && ((*pObj)[0]->data.misc.ubBombOwner - 2)->bTeam == gbPlayerNum ) { gMercProfiles[((*pObj)[0]->data.misc.ubBombOwner - 2)->ubProfile].records.usExpDetonated++; StatChange( ((*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 ( ((*pObj)[0]->data.misc.ubBombOwner - 2)->ubProfile != NO_PROFILE && ((*pObj)[0]->data.misc.ubBombOwner - 2)->bTeam == gbPlayerNum ) { gMercProfiles[((*pObj)[0]->data.misc.ubBombOwner - 2)->ubProfile].records.usExpDetonated++; StatChange( ((*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( SoldierID 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( SoldierID 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 && ItemIsATMine(pObj->usItem)) { // if this is not a vehicle, not a robot and not a tank, don't activate if ( !(ubID->flags.uiStatusFlags & SOLDIER_VEHICLE) && !AM_A_ROBOT( ubID ) && !ARMED_VEHICLE( ubID ) && !ENEMYROBOT( 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 (ItemIsTripwire(pObj->usItem) || (pObj->usItem == ACTION_ITEM && (*pObj)[0]->data.misc.bActionValue == ACTION_ITEM_BLOW_UP && ItemIsTripwire((*pObj)[0]->data.misc.usBombItem) ) ) { 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 ( (ubID->bTeam != gbPlayerNum) && ((*pObj)[0]->data.misc.ubBombOwner > 1) ) { if ( ((*pObj)[0]->data.misc.ubBombOwner - 2)->ubProfile != NO_PROFILE && ((*pObj)[0]->data.misc.ubBombOwner - 2)->bTeam == gbPlayerNum ) gMercProfiles[ ((*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( SoldierID 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= 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, SoldierID ubOwner ) { SOLDIERTYPE * pSoldier; if ( ubOwner == NOBODY ) { return; } if ( ubOwner->bTeam != gbPlayerNum ) { return; } // no point doing this if this is not part of a room if ( sGridNo == NOWHERE || !InARoom( sGridNo, NULL ) ) return; SoldierID cnt = gTacticalStatus.Team[ CIV_TEAM ].bFirstID; for ( ; cnt <= gTacticalStatus.Team[ CIV_TEAM ].bLastID; ++cnt ) { pSoldier = cnt; 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, 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( SoldierID 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 (ItemIsDirectional(usItem)) { // 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, sY; ConvertGridNoToCenterCellXY(gridno, &sX, &sY); 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 ) { //Sewe if there is a person at the gridno SoldierID ubID = WhoIsThere2( sGridNo, 0 ); //is it a valid person if ( (ubID != NOBODY) && (ubID->bTeam == gbPlayerNum) ) { SOLDIERTYPE *pSoldier = 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 SoldierID bID = RandomSoldierIdFromNewMercsOnPlayerTeam(); if( bID != NOBODY ) { DelayedMercQuote( 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 SoldierID sID = WhoIsThere2( sGridNo, 0 ); if( sID != NOBODY && IsSoldierQualifiedMerc( sID ) ) { } else { //Get a random merc to say quote sID = RandomSoldierIdFromNewMercsOnPlayerTeam(); } if( sID != NOBODY ) { //say the quote TacticalCharacterDialogue( 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; SoldierID bID = RandomSoldierIdFromNewMercsOnPlayerTeam(); if( bID != NOBODY ) { TacticalCharacterDialogue( bID, QUOTE_COMMENT_BEFORE_HANG_UP ); } } void HandleSeeingPowerGenFan( UINT32 sGridNo ) { SoldierID 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 = 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(SoldierID 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 (ItemIsFlare(Item[usItem].usBuddyItem)) { 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( SoldierID 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( (ItemHasTripwireActivation(pObj->usItem) && ItemHasTripwireActivation(iter->usItem)) || ( binderFound && detonator && Explosive[Item[iter->usItem].ubClassIndex].ubVolatility > 0 ) ) { if(ItemIsDirectional(iter->usItem) && 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(ItemIsGascan(iter->usItem)) { 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(ItemIsMarbles(iter->usItem)) { 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( (ItemHasTripwireActivation(usItem) && ItemHasTripwireActivation(iter->usItem)) || ( 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 gridnoarmourpair; typedef std::vector< gridnoarmourpair > gridnoarmourvector; typedef std::map 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. SoldierID ubId = WhoIsThere2( sGridNo, 0 ); if ( ubId != NOBODY ) { pSoldier = 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 = 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 ) ) { INT16 sX, sY; ConvertGridNoToCenterCellXY(sGridNo, &sX, &sY); InternalIgniteExplosion( NOBODY, sX, sY, 0, sGridNo, usRoofCollapseExplosionIndex, FALSE, 0 ); } if ( Chance( 15 ) ) { static UINT16 debrissmokeitem = GetFirstExplosiveOfType( EXPLOSV_SMOKE_DEBRIS ); if ( debrissmokeitem ) { INT16 sX, sY; ConvertGridNoToCenterCellXY(sGridNo, &sX, &sY); InternalIgniteExplosion( NOBODY, sX, sY, 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 ); }