From e9a1f4b35211519ddd3b88ad06507d6d5951823c Mon Sep 17 00:00:00 2001 From: Sevenfm Date: Tue, 18 Oct 2016 12:39:06 +0000 Subject: [PATCH] New feature: delayed grenade explosions: - use transformation menu to change explosion mode - only Normal/Stun/Flashbang type grenades can be delayed - AI soldiers will avoid staying close to armed grenades\explosives - use option DELAYED_GRENADE_EXPLOSION to make all hand\GL grenades work as delayed - new value AP_GRENADE_MODE = 4 in APBPConstants.ini - new item flag DELAYED_GRENADE_EXPLOSION Other changes: - fixed call to sqrt in DrawExplosionWarning() that prevented compiling in VS2010 - correctly update interface after transformations - PATHAI: AI soldiers will skip gassed tiles if not in gas already - InGas check: AI soldiers will avoid mustard gas even when wearing gas mask - Yellow AI: added check to avoid gas/water/bomb using FindNearestUngassedLand - gas/flare grenades will always explode instantly even when in bad state - LegalNPCDestination: locations near armed bombs are not legal - FindBestNearbyCover: avoid locations near armed bombs git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@8324 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- GameSettings.cpp | 3 + GameSettings.h | 1 + Init.h | 1 + Tactical/Interface Control.cpp | 2 +- Tactical/Interface Items.cpp | 139 ++++++++++++++++++++++++++++++++- Tactical/Item Types.h | 1 + Tactical/Items.cpp | 28 ++++++- Tactical/Items.h | 6 +- Tactical/PATHAI.cpp | 11 +++ TacticalAI/AIUtils.cpp | 53 ++++++++++++- TacticalAI/DecideAction.cpp | 24 ++++-- TacticalAI/FindLocations.cpp | 6 ++ TacticalAI/Movement.cpp | 35 +++------ TacticalAI/ai.h | 2 + TileEngine/physics.cpp | 108 +++++++++++++------------ Utils/_ChineseText.cpp | 4 + Utils/_DutchText.cpp | 4 + Utils/_EnglishText.cpp | 4 + Utils/_FrenchText.cpp | 4 + Utils/_GermanText.cpp | 4 + Utils/_ItalianText.cpp | 4 + Utils/_PolishText.cpp | 4 + Utils/_RussianText.cpp | 4 + 23 files changed, 365 insertions(+), 87 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index 41edc0ea0..28c3e0be7 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1478,6 +1478,8 @@ void LoadGameExternalOptions() // , and add special bonuses to explosion gGameExternalOptions.bAllowSpecialExplosiveAttachments = iniReader.ReadBoolean("Tactical Interface Settings","ALLOW_SPECIAL_EXPLOSIVE_ATTACHMENTS",FALSE); + gGameExternalOptions.fDelayedGrenadeExplosion = iniReader.ReadBoolean("Tactical Interface Settings","DELAYED_GRENADE_EXPLOSION",FALSE); + // Chance to Say Annoying Phrase (you can just turn of it by button in game) gGameExternalOptions.iChanceSayAnnoyingPhrase = iniReader.ReadInteger("Tactical Interface Settings","CHANCE_SAY_ANNOYING_PHRASE",100); @@ -3263,6 +3265,7 @@ void LoadGameAPBPConstants() APBPConstants[BAD_AP_COST] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","BAD_AP_COST",36),36); APBPConstants[AP_RELOAD_LOOSE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_RELOAD_LOOSE",8),8); APBPConstants[AP_UNJAM] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_UNJAM",2),2); + APBPConstants[AP_GRENADE_MODE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_GRENADE_MODE",4),4); // WANNE: APs needed when you jump through a window APBPConstants[AP_JUMPWINDOW] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_JUMPWINDOW",40),40); diff --git a/GameSettings.h b/GameSettings.h index 3c4b79daf..b4d23bf63 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -867,6 +867,7 @@ typedef struct BOOLEAN bAddSmokeAfterExplosion; BOOLEAN bAllowExplosiveAttachments; BOOLEAN bAllowSpecialExplosiveAttachments; + BOOLEAN fDelayedGrenadeExplosion; INT16 iChanceSayAnnoyingPhrase; BOOLEAN bNewTacticalAIBehavior; BOOLEAN fAIYellowFlanking; diff --git a/Init.h b/Init.h index 562b873e0..95236fc4f 100644 --- a/Init.h +++ b/Init.h @@ -99,6 +99,7 @@ AP_SEVENTH_CLICK_AIM_SCOPE, AP_EIGHTH_CLICK_AIM_SCOPE, AP_RELOAD_LOOSE, AP_UNJAM, +AP_GRENADE_MODE, AP_JUMPWINDOW, //WANNE: jump window APs diff --git a/Tactical/Interface Control.cpp b/Tactical/Interface Control.cpp index 446460a1a..91a45fe08 100644 --- a/Tactical/Interface Control.cpp +++ b/Tactical/Interface Control.cpp @@ -636,7 +636,7 @@ void DrawExplosionWarning( INT32 sGridno, INT8 usLevel, INT8 usDelay ) { for ( INT32 y = yl; y <= yh; ++y ) { - FLOAT diff = std::sqrt( (sScreenX - x) * (sScreenX - x) + 2 * (sScreenY - y) * (sScreenY - y) ); + FLOAT diff = std::sqrt( (FLOAT)((sScreenX - x) * (sScreenX - x) + 2 * (sScreenY - y) * (sScreenY - y)) ); if ( radius_inner <= diff && diff <= radius_outer ) { diff --git a/Tactical/Interface Items.cpp b/Tactical/Interface Items.cpp index 929a4fbe9..11eefd545 100644 --- a/Tactical/Interface Items.cpp +++ b/Tactical/Interface Items.cpp @@ -82,6 +82,9 @@ #include "Explosion Control.h" // added by Flugente #include "Food.h" // added by Flugente #include "Encyclopedia_new.h" //Moa: enc. item visibility + // sevenfm: + #include "Soldier Control.h" + #include "Sound Control.h" #endif #include "Multi Language Graphic Utils.h" @@ -341,6 +344,11 @@ BOOLEAN TransformationMenuPopup_Arm_TestValid(OBJECTTYPE * pObj); void BombInventoryMessageBoxCallBack( UINT8 ubExitValue ); void BombInventoryDisArmMessageBoxCallBack( UINT8 ubExitValue ); +// sevenfm: +// delayed grenade explosion +BOOLEAN TransformationMenuPopup_DelayedGrenadeExplosion_TestValid(OBJECTTYPE* pObj); +void TransformationMenuPopup_DelayedGrenadeExplosion(); + // HEADROCK HAM 5: The maximum number of attachment asterisks shown for an item. UINT32 guiAttachmentAsterisks; #define MAX_NUM_ASTERISKS 10 @@ -4266,6 +4274,26 @@ void INVRenderItem( UINT32 uiBuffer, SOLDIERTYPE * pSoldier, OBJECTTYPE *pObjec gprintfinvalidate( sNewX, sNewY, pStr ); } + // sevenfm: display asterisk if grenade is delayed + if( Item[pObject->usItem].usItemClass == IC_GRENADE && (*pObject)[0]->data.sObjectFlag & DELAYED_GRENADE_EXPLOSION ) + { + sNewY = sY + sHeight - 10; + SetRGBFontForeground( 120, 120, 120 ); + swprintf( pStr, L"*" ); + + // Get length of string + uiStringLength=StringPixLength(pStr, ITEM_FONT ); + + sNewX = sX + sWidth - uiStringLength - 4; + + if ( uiBuffer == guiSAVEBUFFER ) + { + RestoreExternBackgroundRect( sNewX, sNewY, 15, 15 ); + } + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + } + // Flugente: if ammo is used to feed a gun externally, show ammo count left on this ammo if ( gGameExternalOptions.ubExternalFeeding > 0 && (Item[pObject->usItem].usItemClass & (IC_AMMO)) && ObjectIsExternalFeeder(pSoldier, pObject) ) { @@ -6844,8 +6872,9 @@ void RenderItemDescriptionBox( ) // - the item is a bomb and has a detonator or remote detonator attached BOOLEAN renderTransformIcon = FALSE; if ( ((guiCurrentScreen == GAME_SCREEN || guiCurrentScreen == MAP_SCREEN) && gpItemDescObject->ubNumberOfObjects == 1) && - ((Item[gpItemDescObject->usItem].usItemClass == IC_GRENADE) || - ((Item[gpItemDescObject->usItem].usItemClass == IC_BOMB) && HasAttachmentOfClass( gpItemDescObject, (AC_DETONATOR | AC_REMOTEDET) ))) ) + ((Item[gpItemDescObject->usItem].usItemClass == IC_GRENADE) || + ((Item[gpItemDescObject->usItem].usItemClass == IC_BOMB) && HasAttachmentOfClass( gpItemDescObject, (AC_DETONATOR | AC_REMOTEDET) )) || + CanDelayGrenadeExplosion(gpItemDescObject->usItem) && Item[ gpItemDescObject->usItem ].ubCursor == TOSSCURS) ) { renderTransformIcon = TRUE; } @@ -13400,6 +13429,37 @@ void ItemDescTransformRegionCallback( MOUSE_REGION *pRegion, INT32 reason ) // onyl allow transformations if the item is not an armed bomb if ( !fHaveToDisarm ) { + // Check if grenade can be delayed + // don't allow this transformation if player selected option to make all grenades work in delayed mode + if( !gGameExternalOptions.fDelayedGrenadeExplosion && + CanDelayGrenadeExplosion(gpItemDescObject->usItem) && + Item[ gpItemDescObject->usItem ].ubCursor == TOSSCURS ) + { + UINT16 usAPCost = APBPConstants[AP_GRENADE_MODE]; + CHAR16 MenuRowText[300]; + + if ( usAPCost > 0 && gTacticalStatus.uiFlags & INCOMBAT && gTacticalStatus.uiFlags & TURNBASED ) + { + if( (*gpItemDescObject)[0]->data.sObjectFlag & DELAYED_GRENADE_EXPLOSION ) + swprintf (MenuRowText, gzTransformationMessage[13], usAPCost ); + else + swprintf (MenuRowText, gzTransformationMessage[14], usAPCost ); + } + else + { + if( (*gpItemDescObject)[0]->data.sObjectFlag & DELAYED_GRENADE_EXPLOSION ) + swprintf (MenuRowText, gzTransformationMessage[11]); + else + swprintf (MenuRowText, gzTransformationMessage[12]); + } + + // Add option + POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( MenuRowText ), new popupCallbackFunction( &TransformationMenuPopup_DelayedGrenadeExplosion )); + pOption->setAvail(new popupCallbackFunction( &TransformationMenuPopup_DelayedGrenadeExplosion_TestValid, gpItemDescObject )); + gItemDescTransformPopup->addOption( *pOption ); + fFoundTransformations = true; + } + for ( INT32 x = 0; x < gMAXITEMS_READ; ++x ) { if (Transform[x].usItem == (UINT16)-1) @@ -13564,6 +13624,12 @@ BOOLEAN TransformationMenuPopup_TestValid(TransformInfoStruct * Transform) // Flugente: This function handles callback when the 'ARM' option in the item transformation menu is clicked void TransformationMenuPopup_Arm( OBJECTTYPE* pObj ) { + // sevenfm: hide transformation menu + if (gItemDescTransformPopup != NULL && gfItemDescTransformPopupInitialized == TRUE) + { + gItemDescTransformPopup->hide(); + } + // cant handle item stacks here if (gpItemDescObject->ubNumberOfObjects > 1) { @@ -13625,6 +13691,11 @@ void TransformationMenuPopup_Arm( OBJECTTYPE* pObj ) DeleteObj( pObj ); + // sevenfm: correctly update interface + gfSkipDestroyTransformPopup = TRUE; + DeleteItemDescriptionBox(); + gfSkipDestroyTransformPopup = FALSE; + return; } @@ -13779,6 +13850,11 @@ void BombInventoryMessageBoxCallBack( UINT8 ubExitValue ) DeleteObj( gpItemDescObject ); + // sevenfm: correctly update interface + gfSkipDestroyTransformPopup = TRUE; + DeleteItemDescriptionBox(); + gfSkipDestroyTransformPopup = FALSE; + return; } } @@ -13957,6 +14033,11 @@ void BombInventoryDisArmMessageBoxCallBack( UINT8 ubExitValue ) DeleteObj( gpItemDescObject ); + // sevenfm: correctly update interface + gfSkipDestroyTransformPopup = TRUE; + DeleteItemDescriptionBox(); + gfSkipDestroyTransformPopup = FALSE; + #ifdef JA2TESTVERSION ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Arming failed, explosion here" ); #endif @@ -14461,4 +14542,56 @@ void UpdateMercBodyRegionHelpText( ) SetRegionFastHelpText( &gSMInvCamoRegion, sString ); } -} \ No newline at end of file +} + +BOOLEAN TransformationMenuPopup_DelayedGrenadeExplosion_TestValid(OBJECTTYPE* pObj) +{ + if (pObj == NULL) + { + return false; + } + else + { + UINT16 usAPCost = APBPConstants[AP_GRENADE_MODE]; + INT32 iBPCost = 0; + + if (EnoughPoints( gpItemDescSoldier, usAPCost, iBPCost, false )) + { + return true; + } + else + { + return false; + } + } +} + +// sevenfm: change status of delayed grenade explosion +void TransformationMenuPopup_DelayedGrenadeExplosion() +{ + UINT16 usAPCost = APBPConstants[AP_GRENADE_MODE]; + INT32 iBPCost = 0; + + if (gItemDescTransformPopup != NULL && gfItemDescTransformPopupInitialized == TRUE) + { + gItemDescTransformPopup->hide(); + } + + if(EnoughPoints(gpItemDescSoldier, usAPCost, iBPCost, FALSE)) + { + DeductPoints(gpItemDescSoldier, usAPCost, iBPCost); + + if( (*gpItemDescObject)[0]->data.sObjectFlag & DELAYED_GRENADE_EXPLOSION ) + { + (*gpItemDescObject)[0]->data.sObjectFlag &= ~DELAYED_GRENADE_EXPLOSION; + } + else + { + (*gpItemDescObject)[0]->data.sObjectFlag |= DELAYED_GRENADE_EXPLOSION; + } + PlayJA2Sample( ATTACH_TO_GUN, RATE_11025, SoundVolume( MIDVOLUME, gpItemDescSoldier->sGridNo ), 1, SoundDir( gpItemDescSoldier->sGridNo ) ); + + RenderItemDescriptionBox(); + } +} + diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index 6695095c8..01cccb833 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -820,6 +820,7 @@ extern OBJECTTYPE gTempObject; #define TAKEN_BY_MILITIA_TABOO_BLUE 0x0000000800000000 // 34359738368 // this item is taboo for blue militia (have to reset flag for world item upon dropping it) #define INFECTED 0x0000001000000000 // // this item is infected with disease 0, getting damaged by this will infect you +#define DELAYED_GRENADE_EXPLOSION 0x0000002000000000 // // grenade will blow up on next turn // Flugente TODO 2012-09-17: next time we break savegame compatibility, extend the flagmasks from UINT32 to UINT64. I didn't do it this time (see double-used flag above), as we try to minimise those breaks. But it is needed. // ---------------------------------------------------------------- diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index 0afa4dcd9..a82f9ec32 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -15568,4 +15568,30 @@ FLOAT GetAttackAPTraitMultiplier( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj, UINT8 } return fMultiplier; -} \ No newline at end of file +} + +// check if item is grenade and it can use delayed explosion +BOOLEAN CanDelayGrenadeExplosion( UINT16 usItem ) +{ + // check that item is a grenade + if( Item[ usItem ].usItemClass != IC_GRENADE ) + { + return FALSE; + } + + // check that grenade type is not gas/smoke/fire or flare + if( Explosive[Item[ usItem ].ubClassIndex].ubType != EXPLOSV_NORMAL && + Explosive[ Item[ usItem ].ubClassIndex ].ubType != EXPLOSV_STUN && + Explosive[ Item[ usItem ].ubClassIndex ].ubType != EXPLOSV_FLASHBANG ) + { + return FALSE; + } + + // check if grenade should always explode on impact + if( Explosive[Item[ usItem ].ubClassIndex].fExplodeOnImpact ) + { + return FALSE; + } + + return TRUE; +} diff --git a/Tactical/Items.h b/Tactical/Items.h index e2a2d1e38..c56124bbb 100644 --- a/Tactical/Items.h +++ b/Tactical/Items.h @@ -548,7 +548,7 @@ BOOLEAN ObjectIsMilitiaRelevant( OBJECTTYPE *pObj ); FLOAT GetAttackAPTraitMultiplier( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj, UINT8 ubMode); +// sevenfm: check if this type of grenade can use delayed mode +BOOLEAN CanDelayGrenadeExplosion( UINT16 usItem ); + #endif - - - diff --git a/Tactical/PATHAI.cpp b/Tactical/PATHAI.cpp index eeafd4ddc..1f9b706ab 100644 --- a/Tactical/PATHAI.cpp +++ b/Tactical/PATHAI.cpp @@ -60,6 +60,9 @@ class SOLDIERTYPE; extern UINT16 gubAnimSurfaceIndex[ TOTALBODYTYPES ][ NUMANIMATIONSTATES ]; +// sevenfm: +extern BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo ); + //extern UINT8 gubDiagCost[20]; // skiplist has extra level of pointers every 4 elements, so a level 5is optimized for // 4 to the power of 5 elements, or 2 to the power of 10, 1024 @@ -3128,6 +3131,14 @@ if(!GridNoOnVisibleWorldTile(iDestination)) goto NEXTDIR; } + // sevenfm: skip gas if not in gas already + if( !(s->flags.uiStatusFlags & SOLDIER_PC) && + InGas(s, newLoc) && + !InGas(s, s->sGridNo) ) + { + goto NEXTDIR; + } + // WANNE: Know mines (for enemy or player) do not explode - BEGIN if ( gpWorldLevelData[newLoc].uiFlags & (MAPELEMENT_ENEMY_MINE_PRESENT | MAPELEMENT_PLAYER_MINE_PRESENT) ) { diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 9f2a88cdb..3895970a0 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -2011,7 +2011,8 @@ INT16 InWaterOrGas(SOLDIERTYPE *pSoldier, INT32 sGridNo) } BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo ) -{//WarmSteel - One square away from gas is still considered in gas, because it could expand any moment. +{ + //WarmSteel - One square away from gas is still considered in gas, because it could expand any moment. //Note: this only works for gas that expands with one tile, but hey it's better than nothing! int iNeighbourGridNo; for(int iDir = 0; iDir < NUM_WORLD_DIRECTIONS; ++iDir) @@ -2020,12 +2021,13 @@ BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo ) if(!TileIsOutOfBounds(iNeighbourGridNo)) { // tear/mustard gas - if((gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909 + if((gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909 { return(TRUE); } + // sevenfm: avoid mustard gas even when wearing gas mask // fire/creature gas - if(gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_BURNABLEGAS|MAPELEMENT_EXT_CREATUREGAS))//dnl ch62 240813 + if(gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_MUSTARDGAS|MAPELEMENT_EXT_BURNABLEGAS|MAPELEMENT_EXT_CREATUREGAS))//dnl ch62 240813 { return(TRUE); } @@ -3979,3 +3981,48 @@ UINT8 CountTeamSeeSoldier( INT8 bTeam, SOLDIERTYPE *pSoldier ) return ubFriends; } + +BOOLEAN FindBombNearby( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ) +{ + UINT32 uiBombIndex; + INT32 sCheckGridno; + OBJECTTYPE *pObj; + + INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; + + // determine maximum horizontal limits + sMaxLeft = min( ubDistance, (sGridNo % MAXCOL)); + sMaxRight = min( ubDistance, MAXCOL - ((sGridNo % MAXCOL) + 1)); + + // determine maximum vertical limits + sMaxUp = min( ubDistance, (sGridNo / MAXROW)); + sMaxDown = min( ubDistance, MAXROW - ((sGridNo / MAXROW) + 1)); + + for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++) + { + for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) + { + sCheckGridno = sGridNo + sXOffset + (MAXCOL * sYOffset); + + if( TileIsOutOfBounds(sCheckGridno) ) + { + continue; + } + + // search all bombs that we can see + for (uiBombIndex = 0; uiBombIndex < guiNumWorldBombs; uiBombIndex++) + { + if (gWorldBombs[ uiBombIndex ].fExists && + gWorldItems[ gWorldBombs[ uiBombIndex ].iItemIndex ].sGridNo == sCheckGridno && + gWorldItems[ gWorldBombs[ uiBombIndex ].iItemIndex ].ubLevel == pSoldier->pathing.bLevel && + gWorldItems[ gWorldBombs[ uiBombIndex ].iItemIndex ].bVisible == VISIBLE && + gWorldItems[ gWorldBombs[ uiBombIndex ].iItemIndex ].usFlags & WORLD_ITEM_ARMED_BOMB ) + { + return TRUE; + } + } + } + } + + return FALSE; +} diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 66671be44..b74870b90 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -1084,7 +1084,7 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: get out of water and gas")); - if (bInWater || bInGas) + if (bInWater || bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8)) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); @@ -1543,11 +1543,11 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) BOOLEAN fReachable; #ifdef DEBUGDECISIONS STR16 tempstr; -#endif +#endif // Flugente: to prevent an accidental call if ( pSoldier->IsZombie() ) - return( ZombieDecideActionYellow(pSoldier) ); + return( ZombieDecideActionYellow(pSoldier) ); if (fCivilian || (gGameExternalOptions.fAllNamedNpcsDecideAction && pSoldier->ubProfile != NO_PROFILE)) { @@ -1602,6 +1602,20 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) } } + //////////////////////////////////////////////////////////////////////////// + // WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND + //////////////////////////////////////////////////////////////////////////// + + if ( InGas(pSoldier, pSoldier->sGridNo) || DeepWater( pSoldier->sGridNo, pSoldier->pathing.bLevel ) || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8) ) + { + pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + return(AI_ACTION_LEAVE_WATER_GAS); + } + } + // determine the most important noise heard, and its relative value sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable); //NumMessage("iNoiseValue = ",iNoiseValue); @@ -2537,7 +2551,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // WHEN IN GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND //////////////////////////////////////////////////////////////////////////// - if (bInGas && ubCanMove) + if (ubCanMove && (bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8))) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); @@ -4712,7 +4726,7 @@ INT16 ubMinAPCost; //////////////////////////////////////////////////////////////////////////// // if soldier in water/gas has enough APs left to move at least 1 square - if ( ( bInDeepWater || bInGas ) && ubCanMove) + if ( ( bInDeepWater || bInGas || FindBombNearby(pSoldier, pSoldier->sGridNo, DAY_VISION_RANGE/8) ) && ubCanMove) { pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index fd64ce57f..c953ffbc2 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -987,6 +987,12 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB continue; } + // sevenfm: avoid tiles near bombs + if( FindBombNearby(pSoldier, sGridNo, DAY_VISION_RANGE / 8)) + { + continue; + } + iPathCost = gubAIPathCosts[AI_PATHCOST_RADIUS + sXOffset][AI_PATHCOST_RADIUS + sYOffset]; /* // water is OK, if the only good hiding place requires us to get wet, OK diff --git a/TacticalAI/Movement.cpp b/TacticalAI/Movement.cpp index c7409299b..e131935d6 100644 --- a/TacticalAI/Movement.cpp +++ b/TacticalAI/Movement.cpp @@ -59,28 +59,19 @@ int LegalNPCDestination(SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubPathMode, // skip mercs if turnbased and adjacent AND not doing an IGNORE_PATH check (which is used almost exclusively by GoAsFarAsPossibleTowards) fSkipTilesWithMercs = (gfTurnBasedAI && ubPathMode != IGNORE_PATH && SpacesAway( pSoldier->sGridNo, sGridNo ) == 1 ); - // if this gridno is an OK destination - // AND the gridno is NOT in a tear-gassed tile when we have no gas mask - // AND someone is NOT already standing there - // AND we're NOT already standing at that gridno - // AND the gridno hasn't been black-listed for us - - // Nov 28 98: skip people in destination tile if in turnbased - if ( ( NewOKDestination(pSoldier, sGridNo, fSkipTilesWithMercs, pSoldier->pathing.bLevel ) ) && - ( !InGas( pSoldier, sGridNo ) ) && - ( sGridNo != pSoldier->sGridNo ) && - ( sGridNo != pSoldier->pathing.sBlackList ) ) - /* - if ( ( NewOKDestination(pSoldier, sGridno, FALSE, pSoldier->pathing.bLevel ) ) && - ( !(gpWorldLevelData[ sGridno ].ubExtFlags[0] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS)) || ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK || pSoldier->inv[ HEAD2POS ].usItem == GASMASK ) ) && - ( sGridno != pSoldier->sGridNo ) && - ( sGridno != pSoldier->pathing.sBlackList ) )*/ - /* - if ( ( NewOKDestination(pSoldier,sGridno,ALLPEOPLE, pSoldier->pathing.bLevel ) ) && - ( !(gpWorldLevelData[ sGridno ].ubExtFlags[0] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_TEARGAS | MAPELEMENT_EXT_MUSTARDGAS)) || ( pSoldier->inv[ HEAD1POS ].usItem == GASMASK || pSoldier->inv[ HEAD2POS ].usItem == GASMASK ) ) && - ( sGridno != pSoldier->sGridNo ) && - ( sGridno != pSoldier->pathing.sBlackList ) ) - */ + // if this gridno is an OK destination + // AND the gridno is NOT in a tear-gassed tile when we have no gas mask + // AND someone is NOT already standing there + // AND we're NOT already standing at that gridno + // AND the gridno hasn't been black-listed for us + + // Nov 28 98: skip people in destination tile if in turnbased + // sevenfm: also check for bomb nearby + if ( NewOKDestination(pSoldier, sGridNo, fSkipTilesWithMercs, pSoldier->pathing.bLevel ) && + !InGas( pSoldier, sGridNo ) && + !FindBombNearby(pSoldier, sGridNo, DAY_VISION_RANGE/8 ) && + sGridNo != pSoldier->sGridNo && + sGridNo != pSoldier->pathing.sBlackList ) { // if water's a problem, and gridno is in a water tile (bridges are OK) if (!ubWaterOK && Water(sGridNo, pSoldier->pathing.bLevel)) diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index 36d3c72b7..fad1bc7c0 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -295,6 +295,8 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier); BOOLEAN ProneSightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); BOOLEAN SightCoverAtSpot( SOLDIERTYPE *pSoldier, INT32 sSpot ); +BOOLEAN FindBombNearby( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubDistance ); + #define MAX_FLANKS_RED 25 #define MAX_FLANKS_YELLOW 25 diff --git a/TileEngine/physics.cpp b/TileEngine/physics.cpp index 3123fd277..a10576a6c 100644 --- a/TileEngine/physics.cpp +++ b/TileEngine/physics.cpp @@ -2567,18 +2567,21 @@ BOOLEAN DoCatchObject( REAL_OBJECT *pObject ) } -//#define TESTDUDEXPLOSIVES - void HandleArmedObjectImpact( REAL_OBJECT *pObject ) { - INT16 sZ; - BOOLEAN fDoImpact = FALSE; - BOOLEAN fCheckForDuds = FALSE; - bool fIsDud = FALSE; + INT16 sZ; + BOOLEAN fDoImpact = FALSE; + BOOLEAN fCheckForDuds = FALSE; + bool fIsDud = FALSE; OBJECTTYPE *pObj; - INT32 iTrapped = 0; - UINT16 usFlags = 0; - INT8 bLevel = 0; + INT32 iTrapped = 0; + UINT16 usFlags = 0; + INT8 bLevel = 0; + + // sevenfm: can delay explosion for normal, stun and flashbang type grenades + BOOLEAN fCanDelayExplosion = FALSE; + BOOLEAN fGoodStatus = FALSE; + BOOLEAN fDelayedExplosion = FALSE; if (is_networked && is_client) { @@ -2600,6 +2603,16 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) if ( Item[ pObj->usItem ].usItemClass & IC_GRENADE ) { fCheckForDuds = TRUE; + + if( CanDelayGrenadeExplosion(pObj->usItem) && ( Item[pObj->usItem].ubCursor == TOSSCURS || Item[pObj->usItem].glgrenade )) + { + fCanDelayExplosion = TRUE; + } + + if( (*pObj)[0]->data.sObjectFlag & DELAYED_GRENADE_EXPLOSION ) + { + fDelayedExplosion = TRUE; + } } // if ( pObj->usItem == MORTAR_SHELL ) @@ -2617,6 +2630,12 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) if ( fCheckForDuds && (*pObj)[0]->data.misc.bDetonatorType != BOMB_TIMED ) { + if( (*pObj)[0]->data.objectStatus >= USABLE && + PreRandom(100) < (UINT32) (*pObj)[0]->data.objectStatus + PreRandom( 50 ) ) + { + fGoodStatus = TRUE; + } + // OJW - 20021002 - MP Explosives if (is_networked && is_client && pObject->mpIsFromRemoteClient && pObject->mpHaveClientResult) { @@ -2624,13 +2643,11 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) } else { - // If we landed on anything other than the floor, always! go off... -#ifdef TESTDUDEXPLOSIVES - if ( sZ != 0 || pObject->fInWater ) -#else - if ( sZ != 0 || pObject->fInWater || ( (*pObj)[0]->data.objectStatus >= USABLE && ( PreRandom( 100 ) < (UINT32) (*pObj)[0]->data.objectStatus + PreRandom( 50 ) ) ) ) -#endif + if( sZ != 0 || + pObject->fInWater || + !fCanDelayExplosion || + fGoodStatus && !gGameExternalOptions.fDelayedGrenadeExplosion && !fDelayedExplosion ) { fDoImpact = TRUE; fIsDud = false; @@ -2643,24 +2660,32 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) if (fIsDud) { -#ifdef TESTDUDEXPLOSIVES - if ( 1 ) -#else - if ( (*pObj)[0]->data.objectStatus >= USABLE && PreRandom(100) < (UINT32) (*pObj)[0]->data.objectStatus + PreRandom( 50 ) ) -#endif - { - iTrapped = PreRandom( 4 ) + 2; - } + // Start timed bomb... + usFlags |= WORLD_ITEM_ARMED_BOMB; - if ( iTrapped ) + (*pObj)[0]->data.misc.bDetonatorType = BOMB_TIMED; + if( !fGoodStatus ) { - // Start timed bomb... - usFlags |= WORLD_ITEM_ARMED_BOMB; - - (*pObj)[0]->data.misc.bDetonatorType = BOMB_TIMED; (*pObj)[0]->data.misc.bDelay = (INT8)( 1 + PreRandom( 2 ) ); - (*pObj)[0]->data.misc.usBombItem = pObj->usItem; } + else + { + (*pObj)[0]->data.misc.bDelay = 1; + + // for non-player grenades, add turn so player could disarm grenade or run away + if ( pObject->ubOwner != NOBODY && MercPtrs[pObject->ubOwner]->bTeam != gbPlayerNum ) + { + (*pObj)[0]->data.misc.bDelay++; + } + } + + (*pObj)[0]->data.misc.usBombItem = pObj->usItem; + + (*pObj).fFlags |= OBJECT_ARMED_BOMB; + (*pObj).fFlags |= OBJECT_KNOWN_TO_BE_TRAPPED; + + // set high trap level + (*pObj)[0]->data.bTrap = 10; // ATE: If we have collided with roof last... if ( pObject->iOldCollisionCode == COLLISION_ROOF ) @@ -2669,12 +2694,12 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) } // Add item to pool.... - AddItemToPool( pObject->sGridNo, pObj, INVISIBLE, bLevel, usFlags, 0 ); + AddItemToPool( pObject->sGridNo, pObj, VISIBLE, bLevel, usFlags, 0 ); - // All teams lok for this... + // All teams look for this... NotifySoldiersToLookforItems( ); - if ( pObject->ubOwner != NOBODY ) + if ( pObject->ubOwner != NOBODY && !fGoodStatus ) { MercPtrs[ pObject->ubOwner ]->DoMercBattleSound( (INT8)( BATTLE_SOUND_CURSE1 ) ); } @@ -2689,7 +2714,7 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) { if ( Item[pObject->Obj.usItem].flare ) { - //if the light object will ber created OFF the ground + //if the light object will be created OFF the ground if( pObject->Position.z > 0 && FindBuilding(pObject->sGridNo) ) { //we cannot create the light source above the ground, or on a roof. The system doesnt support it. @@ -2698,30 +2723,17 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) else { // Add a light effect... - NewLightEffect( pObject->sGridNo, (UINT8)Explosive[Item[pObject->Obj.usItem].ubClassIndex].ubDuration , (UINT8)Explosive[Item[pObject->Obj.usItem].ubClassIndex].ubStartRadius ); } } else if ( Item[ pObject->Obj.usItem ].usItemClass & IC_GRENADE ) { - /* ARM: Removed. Rewards even missed throws, and pulling a pin doesn't really teach anything about explosives - if ( MercPtrs[ pObject->ubOwner ]->bTeam == gbPlayerNum && gTacticalStatus.uiFlags & INCOMBAT ) - { - // tossed grenade, not a dud, so grant xp - // EXPLOSIVES GAIN (10): Tossing grenade - if ( pObject->ubOwner != NOBODY ) - { - StatChange( MercPtrs[ pObject->ubOwner ], EXPLODEAMT, 10, FALSE ); - } - } - */ - if( (*pObj)[0]->data.misc.bDetonatorType != BOMB_TIMED ) { IgniteExplosion( pObject->ubOwner, (INT16)pObject->Position.x, (INT16)pObject->Position.y, sZ, pObject->sGridNo, pObject->Obj.usItem, GET_OBJECT_LEVEL( pObject->Position.z - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pObject->sGridNo ].sHeight ) ), DIRECTION_IRRELEVANT, &pObject->Obj ); } } - else if ( Item[ pObject->Obj.usItem ].usItemClass == IC_BOMB ) //if ( pObject->Obj.usItem == MORTAR_SHELL ) + else if ( Item[ pObject->Obj.usItem ].usItemClass == IC_BOMB ) { sZ = (INT16)CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16)pObject->Position.z ); @@ -2738,8 +2750,6 @@ void HandleArmedObjectImpact( REAL_OBJECT *pObject ) } - - BOOLEAN SavePhysicsTableToSaveGameFile( HWFILE hFile ) { UINT32 uiNumBytesWritten=0; diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 89b98fad6..8e4010e18 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -7424,6 +7424,10 @@ STR16 gzTransformationMessage[] = L"%s被拆分成%d个弹夹每个有%d发子弹", //L"%s was split into %d Magazines containing %d rounds each.", L"%s拆分完成并放入%s的背包", //L"%s was split into %s's inventory.", L"%s的背包空间不足,放不下这个口径的弹夹了", //L"There is insufficient room in %s's inventory to fit any magazines of this caliber!", + L"Instant mode", // TODO.Translate + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: This hardcoded text should not be used anymore in the game, because we now have those texts externalized in the "TableData\Email\EmailMercLevelUp.xml" file! diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 8cd95c9ea..ece4d7868 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -7439,6 +7439,10 @@ STR16 gzTransformationMessage[] = L"%s was split into %d Magazines containing %d rounds each.", L"%s was split into %s's inventory.", L"There is insufficient room in %s's inventory to fit any magazines of this caliber!", + L"Instant mode", // TODO.Translate + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 56743c885..f463eee40 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -7425,6 +7425,10 @@ STR16 gzTransformationMessage[] = L"%s was split into %d Magazines containing %d rounds each.", L"%s was split into %s's inventory.", L"There is insufficient room in %s's inventory to fit any magazines of this caliber!", + L"Instant mode", + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: This hardcoded text should not be used anymore in the game, because we now have those texts externalized in the "TableData\Email\EmailMercLevelUp.xml" file! diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index 7c6943fa9..174798c23 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -7434,6 +7434,10 @@ STR16 gzTransformationMessage[] = L"%s a été conditionné en %d chargeurs %d coups.", L"%s a été réparti dans l'inventaire de : %s.", L"%s n'a plus de place dans son inventaire pour des chargeurs de ce calibre !", + L"Instant mode", // TODO.Translate + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index 7d71d865c..591160087 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -7261,6 +7261,10 @@ STR16 gzTransformationMessage[] = L"%s wurde aufgeteilt in %d Magazine, wobei jedes davon %d Schuss enthält.", L"%s wurde aufgeteilt in %s's Inventar.", L"Es ist nicht genügend Platz in %s's Inventar um die Magazine dieses Kalibers abzulegen!", + L"Instant mode", // TODO.Translate + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: These are the email texts Speck sends when one of the 4 new 1.13 MERC mercs have levelled up diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index cd89f7df9..285c23975 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -7430,6 +7430,10 @@ STR16 gzTransformationMessage[] = L"%s was split into %d Magazines containing %d rounds each.", L"%s was split into %s's inventory.", L"There is insufficient room in %s's inventory to fit any magazines of this caliber!", + L"Instant mode", // TODO.Translate + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index df1c9ec5a..feb879073 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -7438,6 +7438,10 @@ STR16 gzTransformationMessage[] = L"%s was split into %d Magazines containing %d rounds each.", L"%s was split into %s's inventory.", L"There is insufficient room in %s's inventory to fit any magazines of this caliber!", + L"Instant mode", // TODO.Translate + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index d9606fc8b..838e35dc6 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -7425,6 +7425,10 @@ STR16 gzTransformationMessage[] = L"%s был разделен на %d магазинов по %d патронов в каждом.", L"%s был разделен и помещен в инвентарь %s.", L"Недостаточно места в инвентаре %s для магазинов данного калибра!", + L"Instant mode", // TODO.Translate + L"Delayed mode", + L"Instant mode (%d AP)", + L"Delayed mode (%d AP)", }; // WANNE: This hardcoded text should not be used anymore in the game, because we now have those texts externalized in the "TableData\Email\EmailMercLevelUp.xml" file!