diff --git a/Tactical/Interface Items.cpp b/Tactical/Interface Items.cpp index a7e4ce3b..e7a3eba0 100644 --- a/Tactical/Interface Items.cpp +++ b/Tactical/Interface Items.cpp @@ -80,6 +80,10 @@ #include "popup_callback.h" // BOB : quick attachment popup #include "popup_class.h" + #include "Campaign.h" // added by Flugente + #include "SkillCheck.h" // added by Flugente + #include "random.h" // added by Flugente + #include "Explosion Control.h" // added by Flugente #endif #ifdef JA2UB @@ -330,6 +334,12 @@ void TransformationMenuPopup_SplitCrateInInventory( ); void TransformFromItemDescBox( TransformInfoStruct * Transform); void ConfirmTransformationMessageBoxCallBack( UINT8 bExitValue ); +// Flugente: +void TransformationMenuPopup_Arm( OBJECTTYPE* pObj ); +BOOLEAN TransformationMenuPopup_Arm_TestValid(OBJECTTYPE * pObj); +void BombInventoryMessageBoxCallBack( UINT8 ubExitValue ); +void BombInventoryDisArmMessageBoxCallBack( UINT8 ubExitValue ); + // HEADROCK HAM 5: The maximum number of attachment asterisks shown for an item. UINT32 guiAttachmentAsterisks; #define MAX_NUM_ASTERISKS 10 @@ -6280,7 +6290,8 @@ void RenderItemDescriptionBox( ) { break; } - if (Transform[x].usItem == gpItemDescObject->usItem) + + if ( (Transform[x].usItem == gpItemDescObject->usItem) || ( (guiCurrentScreen == GAME_SCREEN) || (guiCurrentScreen == MAP_SCREEN) ) && Item[gpItemDescObject->usItem].usItemClass == IC_BOMB && gpItemDescObject->ubNumberOfObjects == 1 && HasAttachmentOfClass( gpItemDescObject, (AC_DETONATOR | AC_REMOTEDET) ) ) { BltVideoObjectFromIndex( guiSAVEBUFFER, guiTransformIconGraphic, 0, (ITEMDESC_ITEM_X+ITEMDESC_ITEM_WIDTH)-13, (ITEMDESC_ITEM_Y+ITEMDESC_ITEM_HEIGHT)-17, VO_BLT_SRCTRANSPARENCY, NULL ); } @@ -12162,36 +12173,96 @@ void ItemDescTransformRegionCallback( MOUSE_REGION *pRegion, INT32 reason ) // Scan the Transformation list for the current item. Create pop-up options as required. INT32 iTransformIndex = -1; - for (INT32 x = 0; x < MAXITEMS; x++) - { - if (Transform[x].usItem == -1) - { - break; - } - if (Transform[x].usItem == gpItemDescObject->usItem) - { - iTransformIndex++; - CHAR16 MenuRowText[300]; - if ( Transform[x].usAPCost > 0 && gTacticalStatus.uiFlags & INCOMBAT && gTacticalStatus.uiFlags & TURNBASED ) + // Flugente: we can also arm/disarm bombs in our inventory via this menu + BOOLEAN fHaveToDisarm = FALSE; // important check: if item is an armed bomb, we have to disarm it prior to any transformation + if ( ( (guiCurrentScreen == GAME_SCREEN) || (guiCurrentScreen == MAP_SCREEN) ) && Item[gpItemDescObject->usItem].usItemClass == IC_BOMB && gpItemDescObject->ubNumberOfObjects == 1 && HasAttachmentOfClass( gpItemDescObject, (AC_DETONATOR | AC_REMOTEDET) ) ) + { + iTransformIndex++; + + UINT16 apcost = 20; + + if ( (guiCurrentScreen == GAME_SCREEN) || (guiCurrentScreen == MAP_SCREEN) ) + apcost = 15; + + // test wether item is already armed + INT8 detonatortype; + INT8 setting; + INT8 defusefrequency; + CheckBombSpecifics( gpItemDescObject, &detonatortype, &setting, &defusefrequency ); + + CHAR16 MenuRowText[300]; + + if ( detonatortype == BOMB_TIMED || detonatortype == BOMB_REMOTE ) + { + fHaveToDisarm = TRUE; + + if ( apcost > 0 && gTacticalStatus.uiFlags & INCOMBAT && gTacticalStatus.uiFlags & TURNBASED ) { - swprintf (MenuRowText, L"%s (%d AP)", Transform[x].szMenuRowText, Transform[x].usAPCost ); + swprintf (MenuRowText, L"Disarm (%d AP)", apcost ); } else { - swprintf (MenuRowText, Transform[x].szMenuRowText); + swprintf (MenuRowText, L"Disarm"); } + } + else + { + if ( apcost > 0 && gTacticalStatus.uiFlags & INCOMBAT && gTacticalStatus.uiFlags & TURNBASED ) + { + swprintf (MenuRowText, L"Arm (%d AP)", apcost ); + } + else + { + swprintf (MenuRowText, L"Arm"); + } + } - // Generate a new option for the menu - POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( MenuRowText ), new popupCallbackFunction( &TransformationMenuPopup_Transform, &Transform[x] ) ); - // Set the function that tests whether it's valid at the moment. - pOption->setAvail(new popupCallbackFunction( &TransformationMenuPopup_TestValid, &Transform[x] )); - // Add the option to the menu. - gItemDescTransformPopup->addOption( *pOption ); - // Set this flag so we know we have at least one Transformation available. - fFoundTransformations = true; + // Generate a new option for the menu + POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( MenuRowText ), new popupCallbackFunction( &TransformationMenuPopup_Arm, gpItemDescObject ) ); + // Set the function that tests whether it's valid at the moment. + pOption->setAvail(new popupCallbackFunction( &TransformationMenuPopup_Arm_TestValid, gpItemDescObject )); + // Add the option to the menu. + gItemDescTransformPopup->addOption( *pOption ); + // Set this flag so we know we have at least one Transformation available. + fFoundTransformations = true; + } + + // onyl allow transformations if the item is not an armed bomb + if ( !fHaveToDisarm ) + { + for (INT32 x = 0; x < MAXITEMS; x++) + { + if (Transform[x].usItem == -1) + { + break; + } + if (Transform[x].usItem == gpItemDescObject->usItem) + { + iTransformIndex++; + + CHAR16 MenuRowText[300]; + if ( Transform[x].usAPCost > 0 && gTacticalStatus.uiFlags & INCOMBAT && gTacticalStatus.uiFlags & TURNBASED ) + { + swprintf (MenuRowText, L"%s (%d AP)", Transform[x].szMenuRowText, Transform[x].usAPCost ); + } + else + { + swprintf (MenuRowText, Transform[x].szMenuRowText); + } + + // Generate a new option for the menu + POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( MenuRowText ), new popupCallbackFunction( &TransformationMenuPopup_Transform, &Transform[x] ) ); + // Set the function that tests whether it's valid at the moment. + pOption->setAvail(new popupCallbackFunction( &TransformationMenuPopup_TestValid, &Transform[x] )); + // Add the option to the menu. + gItemDescTransformPopup->addOption( *pOption ); + // Set this flag so we know we have at least one Transformation available. + fFoundTransformations = true; + } } } + if (!fFoundTransformations) { POPUP_OPTION * pOption = new POPUP_OPTION( &std::wstring( gzTransformationMessage[ 0 ] ), new popupCallbackFunction( &TransformationMenuPopup_Transform, NULL ) ); @@ -12303,6 +12374,15 @@ BOOLEAN TransformationMenuPopup_TestValid(TransformInfoStruct * Transform) if (EnoughPoints( gpItemDescSoldier, usAPCost, iBPCost, false )) { + // Flugente: If item is an armed bomb, do not allow any transformation! + if ( Item[gpItemDescObject->usItem].usItemClass == IC_BOMB && gpItemDescObject->ubNumberOfObjects == 1 && HasAttachmentOfClass( gpItemDescObject, (AC_DETONATOR | AC_REMOTEDET) ) ) + { + if ( (*gpItemDescObject)[0]->data.misc.bDetonatorType == BOMB_TIMED || (*gpItemDescObject)[0]->data.misc.bDetonatorType == BOMB_REMOTE ) + { + return false; + } + } + return true; } else @@ -12312,6 +12392,316 @@ 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 ) +{ + // cant handle item stacks here + if (gpItemDescObject->ubNumberOfObjects > 1) + { + return; + } + else + { + INT8 screen = guiCurrentScreen; + if ( screen != GAME_SCREEN && screen != MAP_SCREEN ) + return; + + // test wether item is already armed + INT8 detonatortype; + INT8 setting; + INT8 defusefrequency; + CheckBombSpecifics( gpItemDescObject, &detonatortype, &setting, &defusefrequency ); + + if ( detonatortype == BOMB_TIMED || detonatortype == BOMB_REMOTE ) + { + // TODO + DoMessageBox( MSG_BOX_BASIC_STYLE, TacticalStr[ DISARM_BOOBYTRAP_PROMPT ], screen, ( UINT8 )MSG_BOX_FLAG_YESNO, BombInventoryDisArmMessageBoxCallBack, NULL ); + } + else if ( HasAttachmentOfClass( gpItemDescObject, (AC_DEFUSE ) ) ) + { + if ( HasAttachmentOfClass( gpItemDescObject, (AC_DETONATOR ) ) ) + { + DoMessageBox( MSG_BOX_BASIC_SMALL_BUTTONS, TacticalStr[ CHOOSE_DETONATE_AND_REMOTE_DEFUSE_FREQUENCY_STR ], screen, MSG_BOX_FLAG_SIXTEEN_NUMBERED_BUTTONS, BombInventoryMessageBoxCallBack, NULL ); + } + else if ( HasAttachmentOfClass( gpItemDescObject, (AC_REMOTEDET ) ) ) + { + DoMessageBox( MSG_BOX_BASIC_SMALL_BUTTONS, TacticalStr[ CHOOSE_REMOTE_DETONATE_AND_REMOTE_DEFUSE_FREQUENCY_STR ], screen, MSG_BOX_FLAG_SIXTEEN_NUMBERED_BUTTONS, BombInventoryMessageBoxCallBack, NULL ); + } + } + else if ( HasAttachmentOfClass( gpItemDescObject, (AC_DETONATOR ) ) ) + { + DoMessageBox( MSG_BOX_BASIC_SMALL_BUTTONS, TacticalStr[ CHOOSE_TIMER_STR ], screen, MSG_BOX_FLAG_FOUR_NUMBERED_BUTTONS, BombInventoryMessageBoxCallBack, NULL ); + } + else if ( HasAttachmentOfClass( gpItemDescObject, (AC_REMOTEDET ) ) ) + { + DoMessageBox( MSG_BOX_BASIC_SMALL_BUTTONS, TacticalStr[ CHOOSE_REMOTE_FREQUENCY_STR ], screen, MSG_BOX_FLAG_FOUR_NUMBERED_BUTTONS, BombInventoryMessageBoxCallBack, NULL ); + } + } +} + +BOOLEAN TransformationMenuPopup_Arm_TestValid(OBJECTTYPE * pObj) +{ + if (pObj == NULL) + { + return false; + } + else + { + UINT16 usAPCost = 20; + INT32 iBPCost = 1; + + if (EnoughPoints( gpItemDescSoldier, usAPCost, iBPCost, false )) + { + return true; + } + else + { + return false; + } + } +} + +void BombInventoryMessageBoxCallBack( UINT8 ubExitValue ) +{ + if (gpItemDescSoldier) + { + // no planting tripwire in our inventory... + if ( Item[ gpItemDescObject->usItem ].tripwire == 1 ) + return; + + INT32 iResult; + + if ( HasAttachmentOfClass( gpItemDescObject, AC_REMOTEDET ) ) + { + iResult = SkillCheck( gpItemDescSoldier, PLANTING_REMOTE_BOMB_CHECK, 0 ); + } + else + { + iResult = SkillCheck( gpItemDescSoldier, PLANTING_BOMB_CHECK, 0 ); + } + + if ( iResult >= 0 ) + { + // EXPLOSIVES GAIN (25): Place a bomb, or buried and armed a mine + StatChange( gpItemDescSoldier, EXPLODEAMT, 25, FALSE ); + } + else + { + // EXPLOSIVES GAIN (10): Failed to place a bomb, or bury and arm a mine + StatChange( gpItemDescSoldier, EXPLODEAMT, 10, FROM_FAILURE ); + + // oops! How badly did we screw up? + if ( iResult >= -20 ) + { + // messed up the setting + if ( ubExitValue == 0 ) + { + ubExitValue = 1; + } + else + { + // change up/down by 1 + ubExitValue = (UINT8) (ubExitValue + Random( 3 ) - 1); + } + // and continue + } + else + { + gpItemDescSoldier->DoMercBattleSound( BATTLE_SOUND_CURSE1 ); + + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Arming of bomb failed. Resulting explosion damages %s's inventory and health", gpItemDescSoldier->name ); + + INT8 screen = guiCurrentScreen; + if ( screen == GAME_SCREEN ) + { + // ignite explosions manually - this item is not in the WorldBombs-structure, so we can't add it to the queue + IgniteExplosion( (*gpItemDescObject)[0]->data.misc.ubBombOwner - 2, gpItemDescSoldier->sX, gpItemDescSoldier->sY, (INT16) (gpWorldLevelData[gpItemDescSoldier->sGridNo].sHeight), gpItemDescSoldier->sGridNo, gpItemDescObject->usItem, gpItemDescSoldier->pathing.bLevel, gpItemDescSoldier->ubDirection ); + + DeleteObj( gpItemDescObject ); + } + else if ( (screen == MAP_SCREEN) || (screen == MSG_BOX_SCREEN) ) + { + // no explosions in map screen - instead we simply damage the inventory and harm our health + gpItemDescSoldier->InventoryExplosion(); + + DeleteObj( gpItemDescObject ); + } + + return; + } + } + + if ( ArmBomb( gpItemDescObject, ubExitValue ) ) + { + // SANDRO - STOMP traits - Demolitions bonus to trap level + if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( gpItemDescSoldier, DEMOLITIONS_NT )) + { + // increase trap level for Demolitions trait + gpItemDescSoldier->inv[ HANDPOS ][0]->data.bTrap = __min( max( 10, (8 + gSkillTraitValues.ubDEPlacedBombLevelBonus)), (( EffectiveExplosive( gpItemDescSoldier ) / 20) + (EffectiveExpLevel( gpItemDescSoldier ) / 3) + gSkillTraitValues.ubDEPlacedBombLevelBonus) ); + } + else + { + gpItemDescSoldier->inv[ HANDPOS ][0]->data.bTrap = __min( 10, ( EffectiveExplosive( gpItemDescSoldier ) / 20) + (EffectiveExpLevel( gpItemDescSoldier ) / 3) ); + } + + // Flugente: We armed a bomb in our inventory. We will NOT add it to the item pool and the world bombs. + // Instead, we will count down the delay time every turn and eventually ignite the bomb from our inventory. + // Same for remote detonators, when sending a signal we will have to also check everyone's inventory and eventually ignite bombs in inventories + + // HACK IMMINENT! + // value of 1 is stored in maps for SIDE of bomb owner... when we want to use IDs! + // so we add 2 to all owner IDs passed through here and subtract 2 later + //if (gpItemDescSoldier->inv[HANDPOS].MoveThisObjectTo(gTempObject, 1) == 0) + { + gTempObject[0]->data.misc.ubBombOwner = gpItemDescSoldier->ubID + 2; + gTempObject[0]->data.ubDirection = gpItemDescSoldier->ubDirection; // Flugente: direction of bomb is direction of soldier + + //AddItemToPool( gsTempGridNo, &gTempObject, VISIBLE, gpItemDescSoldier->pathing.bLevel, WORLD_ITEM_ARMED_BOMB, 0 ); + } + } + } +} + +void BombInventoryDisArmMessageBoxCallBack( UINT8 ubExitValue ) +{ + if ( !gpItemDescSoldier ) + return; + + if ( !gpItemDescObject ) + return; + + if (ubExitValue == MSG_BOX_RETURN_YES) + { + INT32 iCheckResult; + + INT8 trapdifficulty = (*gpItemDescObject)[0]->data.bTrap; + + // Snap: make it easier to disarm our own traps. + // If we succede - we get exp, but if we fail - we pay fair and square! + + //CHRISL: first things first. If we're in combat, we need to spend some APs to disarm the device + if((gTacticalStatus.uiFlags & INCOMBAT) || (gTacticalStatus.fEnemyInSector)) + { + // SANDRO was here, AP_DISARM_MINE changed to GetAPsToDisarmMine + if(EnoughPoints(gpItemDescSoldier, GetAPsToDisarmMine( gpItemDescSoldier ), APBPConstants[BP_DISARM_MINE], TRUE)) + DeductPoints(gpItemDescSoldier, GetAPsToDisarmMine( gpItemDescSoldier ), APBPConstants[BP_DISARM_MINE], AFTERACTION_INTERRUPT); + else + return; + } + + // NB owner grossness... bombs 'owned' by the enemy are stored with side value 1 in + // the map. So if we want to detect a bomb placed by the player, owner is > 1, and + // owner - 2 gives the ID of the character who planted it + if ( (*gpItemDescObject)[0]->data.misc.ubBombOwner > 1 && ( (INT32)(*gpItemDescObject)[0]->data.misc.ubBombOwner - 2 >= gTacticalStatus.Team[ OUR_TEAM ].bFirstID && (*gpItemDescObject)[0]->data.misc.ubBombOwner - 2 <= gTacticalStatus.Team[ OUR_TEAM ].bLastID ) ) + { + // Flugente: get a tripwire-related bonus if we have a wire cutter in our hands + INT8 wirecutterbonus = 0; + if ( ( (&gpItemDescSoldier->inv[HANDPOS])->exists() && Item[ gpItemDescSoldier->inv[HANDPOS].usItem ].wirecutters == 1 ) || ( (&gpItemDescSoldier->inv[SECONDHANDPOS])->exists() && Item[ gpItemDescSoldier->inv[SECONDHANDPOS].usItem ].wirecutters == 1 ) ) + { + // + 10 if item gets activated by tripwire + if ( Item[gpItemDescObject->usItem].tripwireactivation == 1 ) + wirecutterbonus += 10; + + // + 10 if item is tripwire + if ( Item[gpItemDescObject->usItem].tripwire == 1 ) + wirecutterbonus += 10; + } + + if ( (*gpItemDescObject)[0]->data.misc.ubBombOwner - 2 == gpItemDescSoldier->ubID ) + { + // my own boobytrap! + iCheckResult = SkillCheck( gpItemDescSoldier, DISARM_TRAP_CHECK, 40 + wirecutterbonus ); + } + else + { + // our team's boobytrap! + iCheckResult = SkillCheck( gpItemDescSoldier, DISARM_TRAP_CHECK, 20 + wirecutterbonus ); + } + } + else + { + iCheckResult = SkillCheck( gpItemDescSoldier, DISARM_TRAP_CHECK, 0 ); + } + + if (iCheckResult >= 0) + { + + if ( (*gpItemDescObject)[0]->data.misc.ubBombOwner > 1 && ( (INT32)(*gpItemDescObject)[0]->data.misc.ubBombOwner - 2 >= gTacticalStatus.Team[ OUR_TEAM ].bFirstID && (*gpItemDescObject)[0]->data.misc.ubBombOwner - 2 <= gTacticalStatus.Team[ OUR_TEAM ].bLastID ) ) + { + if ( (*gpItemDescObject)[0]->data.misc.ubBombOwner - 2 == gpItemDescSoldier->ubID ) + { + // disarmed my own boobytrap! + StatChange( gpItemDescSoldier, EXPLODEAMT, (UINT16) (2 * trapdifficulty), FALSE ); + } + else + { + // disarmed our team's boobytrap! + StatChange( gpItemDescSoldier, EXPLODEAMT, (UINT16) (4 * trapdifficulty), FALSE ); + + // SANDRO - merc records - trap removal count (don't count our own traps) + gMercProfiles[ gpItemDescSoldier->ubProfile ].records.usTrapsRemoved++; + } + } + else + { + // disarmed a boobytrap! + StatChange( gpItemDescSoldier, EXPLODEAMT, (UINT16) (6 * trapdifficulty), FALSE ); + + // SANDRO - merc records - trap removal count + gMercProfiles[ gpItemDescSoldier->ubProfile ].records.usTrapsRemoved++; + } + + // have merc say this is good + gpItemDescSoldier->DoMercBattleSound( BATTLE_SOUND_COOL1 ); + + (*gpItemDescObject)[0]->data.ubWireNetworkFlag = 0; + (*gpItemDescObject)[0]->data.bDefuseFrequency = 0; + (*gpItemDescObject)[0]->data.misc.bDetonatorType = 0; + (*gpItemDescObject)[0]->data.misc.bDelay = 0; + (*gpItemDescObject)[0]->data.misc.bFrequency = 0; + (*gpItemDescObject)[0]->data.bTrap = 0; + + if ( (*gpItemDescObject).fFlags & OBJECT_KNOWN_TO_BE_TRAPPED ) + gpItemDescObject->fFlags &= ~( OBJECT_KNOWN_TO_BE_TRAPPED ); + + if ( (*gpItemDescObject).fFlags & OBJECT_ARMED_BOMB ) + gpItemDescObject->fFlags &= ~( OBJECT_ARMED_BOMB ); + + if ( (*gpItemDescObject).fFlags & OBJECT_KNOWN_TO_BE_TRAPPED ) + gpItemDescObject->fFlags &= ~( OBJECT_KNOWN_TO_BE_TRAPPED ); + } + else + { + // oops! trap goes off + StatChange( gpItemDescSoldier, EXPLODEAMT, (INT8) (3 * trapdifficulty ), FROM_FAILURE ); + + gpItemDescSoldier->DoMercBattleSound( BATTLE_SOUND_CURSE1 ); + + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Disarming of bomb failed. Resulting explosion damages %s's inventory and health", gpItemDescSoldier->name ); + + INT8 screen = guiCurrentScreen; + if ( screen == GAME_SCREEN ) + { + // ignite explosions manually - this item is not in the WorldBombs-structure, so we can't add it to the queue + IgniteExplosion( (*gpItemDescObject)[0]->data.misc.ubBombOwner - 2, gpItemDescSoldier->sX, gpItemDescSoldier->sY, (INT16) (gpWorldLevelData[gpItemDescSoldier->sGridNo].sHeight), gpItemDescSoldier->sGridNo, gpItemDescObject->usItem, gpItemDescSoldier->pathing.bLevel, gpItemDescSoldier->ubDirection ); + + DeleteObj( gpItemDescObject ); + } + else if ( (screen == MAP_SCREEN) || (screen == MSG_BOX_SCREEN) ) + { + // no explosions in map screen - instead we simply damage the inventory and harm our health + gpItemDescSoldier->InventoryExplosion(); + + DeleteObj( gpItemDescObject ); + } + +#ifdef JA2TESTVERSION + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Arming failed, explosion here" ); +#endif + } + } +} + // HEADROCK HAM 5: This Transformation Menu Callback will attempt to unjam a jammed gun. If the // soldier lacks APs to do so, it will fail with a screen message. Otherwise the gun is unjammed, // causing the normal effects. diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index e2acec7d..cb72bb06 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -472,7 +472,7 @@ public: // these variables should belong to a different position. However, I am forced to put them here, otherwise loading of WF maps and other old data would not work properly FLOAT bTemperature; // Flugente FTW 1.2: temperature of gun - // shoul belong to misc, but was moved here because of the odl maps issue + // should belong to misc, but was moved here because of the old maps issue // added by Flugente 12-04-15 UINT8 ubDirection; // direction the bomb faces (for directional explosives) UINT32 ubWireNetworkFlag; // flags for the tripwire network diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index 704d6e4f..40513551 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -7656,7 +7656,6 @@ BOOLEAN ArmBomb( OBJECTTYPE * pObj, INT8 bSetting ) return( FALSE ); } - // Flugente TODO determine correct frequency and detonate/defuse stuff // Flugente: decide how to interpret the bSetting we just got. // Due to limitations in the message system, we only receive a single value to interpret, as we currently can't have a message box return 2 values // It might be possible to have proper checkboxes, but I'll rather not research this right now. @@ -7669,7 +7668,7 @@ BOOLEAN ArmBomb( OBJECTTYPE * pObj, INT8 bSetting ) // d) if we have a remote detonator plus a remote defuse: frequency on which the bomb will blow plus frequency to defuse: 1-16 // // I we are placing tripwire, consider this: - // e) if we palce tripwire: the tripwire network plus the hierachy in that network: 1-16 + // e) if we place tripwire: the tripwire network plus the hierachy in that network: 1-16 // // It is clear that we only have to reinterpret the values if a defuse is equiped, or we are placing tripwire INT8 detonatesetting = bSetting; @@ -13860,3 +13859,19 @@ UINT64 GetAvailableAttachmentPoint (OBJECTTYPE * pObject, UINT8 subObject) return point; } + +// Flugente: check if and how a bomb has been set up +void CheckBombSpecifics( OBJECTTYPE * pObj, INT8* detonatortype, INT8* setting, INT8* defusefrequency ) +{ + if ( pObj && pObj->exists() ) + { + *detonatortype = (*pObj)[0]->data.misc.bDetonatorType; + + if ( *detonatortype == BOMB_TIMED ) + *setting = (*pObj)[0]->data.misc.bDelay; + else + *setting = (*pObj)[0]->data.misc.bFrequency; + + *defusefrequency = (*pObj)[0]->data.bDefuseFrequency; + } +} diff --git a/Tactical/Items.h b/Tactical/Items.h index 6efda2e0..ca2c700e 100644 --- a/Tactical/Items.h +++ b/Tactical/Items.h @@ -459,4 +459,7 @@ bool IsAttachmentPointAvailable( UINT32 itemID, UINT32 attachmentID ); bool IsAttachmentPointAvailable( UINT64 point, UINT32 attachmentID, BOOLEAN onlyCheckAttachments ); UINT64 GetAvailableAttachmentPoint ( OBJECTTYPE * pObject, UINT8 subObject ); +// Flugente: check if and how a bomb has been set up +void CheckBombSpecifics( OBJECTTYPE * pObj, INT8* detonatortype, INT8* setting, INT8* defusefrequency ); + #endif diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index b354ce66..88956030 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -11529,7 +11529,7 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio if ( pTSoldier->stats.bLife - damage < 0 ) damage = oldlife; - // We've got a problem if we kill someone outright ithout him collapsing properly... + // We've got a problem if we kill someone outright without him collapsing properly... // FIX: We'll adjust our damage, so if we'd kill someone without collapsing first, we lower our damage, to let him collapse // After all, this whole thing's a rig up, so there shouldn't be a problem with that if ( oldlife >= OKLIFE && oldlife <= damage ) @@ -13508,6 +13508,70 @@ void SOLDIERTYPE::ResetExtraStats() bExtraExpLevel = 0; } +// Flugente: inventory bombs can ignite while in mapscreen. Workaround: Damage items and health +void SOLDIERTYPE::InventoryExplosion( void ) +{ + INT8 invsize = (INT8)this->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 (this->inv[bLoop].exists() == true ) + { + OBJECTTYPE * pObj = &(this->inv[bLoop]); // ... get pointer for this item ... + + if ( pObj != NULL ) // ... if pointer is not obviously useless ... + { + for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ... + { + INT16 status = (*pObj)[0]->data.objectStatus; + (*pObj)[0]->data.objectStatus = max(1, (INT16)(status/2)); + + // for every objects, we also have to check wether there are weapon attachments (eg. underbarrel grenade launchers), and cool them down too + attachmentList::iterator iterend = (*pObj)[i]->attachments.end(); + for (attachmentList::iterator iter = (*pObj)[i]->attachments.begin(); iter != iterend; ++iter) + { + if ( iter->exists() ) + { + INT16 status = (*iter)[0]->data.objectStatus; + (*iter)[0]->data.objectStatus = max(1, (INT16)(status/2)); + } + } + } + } + } + } + + // now damage our health + INT8 oldlife = stats.bLife; + + INT16 damage = (INT16)(30 + Random(20)); + if ( stats.bLife - damage < 0 ) + damage = oldlife; + + // We've got a problem if we kill someone outright without him collapsing properly... + // FIX: We'll adjust our damage, so if we'd kill someone without collapsing first, we lower our damage, to let him collapse + // After all, this whole thing's a rig up, so there shouldn't be a problem with that + if ( oldlife >= OKLIFE && oldlife <= damage ) + damage -= (INT16)((5 + Random(5))); + + INT16 breathdamage = (INT16)(500 + Random(1500)); + if ( bBreath - breathdamage < 0 ) + breathdamage = bBreath; + + SoldierTakeDamage( 0, damage, 0, breathdamage, TAKE_DAMAGE_EXPLOSION, this->ubID, sGridNo, 0, TRUE ); + + if ( stats.bLife <= 0 ) + { + // FINISH HIM! + HandleTakeDamageDeath( this, oldlife, TAKE_DAMAGE_BLOODLOSS ); + } + else if ( stats.bLife < OKLIFE && !bCollapsed ) + { + // let the target collapse... + SoldierCollapse(this); + } +} + INT32 CheckBleeding( SOLDIERTYPE *pSoldier ) { diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index f6f4b8f2..8ceb42fb 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -1367,6 +1367,9 @@ public: // reset the extra stat variables void ResetExtraStats(); + + // Flugente: inventory bombs can ignite while in mapscreen. Workaround: Damage items and health + void InventoryExplosion( void ); ////////////////////////////////////////////////////////////////////////////// }; // SOLDIERTYPE; diff --git a/Tactical/World Items.cpp b/Tactical/World Items.cpp index 58627aa6..5d6df8d8 100644 --- a/Tactical/World Items.cpp +++ b/Tactical/World Items.cpp @@ -449,7 +449,8 @@ INT32 AddItemToWorld( INT32 sGridNo, OBJECTTYPE *pObject, UINT8 ubLevel, UINT16 gWorldItems[ iItemIndex ].object = *pObject; // Add a bomb reference if needed - if (usFlags & WORLD_ITEM_ARMED_BOMB) + // Flugente: we can arm bombs in our inventory and then throw them out, which will cause them to be added to the world. Only way to identify those items is via a check for their bDetonatorType + if (usFlags & WORLD_ITEM_ARMED_BOMB || ( Item[pObject->usItem].usItemClass & (IC_BOMB) && ( (*pObject)[0]->data.misc.bDetonatorType == BOMB_TIMED ) || ( (*pObject)[0]->data.misc.bDetonatorType == BOMB_REMOTE ) ) ) { iReturn = AddBombToWorld( iItemIndex ); if (iReturn == -1) diff --git a/TileEngine/Explosion Control.cpp b/TileEngine/Explosion Control.cpp index 5fe70377..20d45eca 100644 --- a/TileEngine/Explosion Control.cpp +++ b/TileEngine/Explosion Control.cpp @@ -3841,7 +3841,7 @@ void DecayBombTimers( void ) OBJECTTYPE * pObj; uiTimeStamp = GetJA2Clock(); - + // Go through all the bombs in the world, and look for timed ones for (uiWorldBombIndex = 0; uiWorldBombIndex < guiNumWorldBombs; uiWorldBombIndex++) { @@ -3881,6 +3881,59 @@ void DecayBombTimers( void ) } } } + + // 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) ) ) + { + OBJECTTYPE * pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ... + + if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_TIMED ) + { + // Found a timed bomb, so decay its delay value and see if it goes off + (*pObj)[0]->data.misc.bDelay--; + if ((*pObj)[0]->data.misc.bDelay == 0) + { + // ATE: CC black magic.... + if ( (*pObj)[0]->data.misc.ubBombOwner > 1 ) + { + gubPersonToSetOffExplosions = (UINT8) ((*pObj)[0]->data.misc.ubBombOwner - 2); + // SANDRO - merc records - detonating explosives + if ( MercPtrs[ gubPersonToSetOffExplosions ]->ubProfile != NO_PROFILE && MercPtrs[ gubPersonToSetOffExplosions ]->bTeam == gbPlayerNum ) + { + gMercProfiles[ MercPtrs[ gubPersonToSetOffExplosions ]->ubProfile ].records.usExpDetonated++; + } + } + else + { + gubPersonToSetOffExplosions = NOBODY; + } + + // ignite explosions manually - this item is not in the WorldBombs-structure, so we can't add it to the queue + IgniteExplosion( gubPersonToSetOffExplosions, pSoldier->sX, pSoldier->sY, (INT16) (gpWorldLevelData[pSoldier->sGridNo].sHeight), pSoldier->sGridNo, pObj->usItem, pSoldier->pathing.bLevel, pSoldier->ubDirection ); + + DeleteObj( pObj ); + } + } + } + } + } + } + } } void SetOffBombsByFrequency( UINT8 ubID, INT8 bFrequency ) @@ -3985,6 +4038,86 @@ void SetOffBombsByFrequency( UINT8 ubID, INT8 bFrequency ) } } } + + // Flugente: new stuff: we can now also arm bombs in our inventory, and detonate/defuse those bombs remotely + // So we have to look at every item in every inventory in this sector + for (UINT32 cnt = 0; cnt < guiNumMercSlots; cnt++ ) + { + SOLDIERTYPE* pSoldier = MercSlots[ cnt ]; + + if ( pSoldier != NULL ) + { + if ( pSoldier->bInSector && pSoldier->bActive ) + { + INT8 invsize = (INT8)pSoldier->inv.size(); // remember inventorysize, so we don't call size() repeatedly + + for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop) // ... for all items in our inventory ... + { + // ... if Item is a bomb ... + if (pSoldier->inv[bLoop].exists() == true && ( Item[pSoldier->inv[bLoop].usItem].usItemClass & (IC_BOMB) ) ) + { + OBJECTTYPE * pObj = &(pSoldier->inv[bLoop]); // ... get pointer for this item ... + + if ( (*pObj)[0]->data.misc.bDetonatorType == BOMB_REMOTE ) + { + // Found a remote bomb, so check to see if it has the same frequency + if ( fDetonate ) // detonate bombs + { + // Found a remote bomb, so check to see if it has the same frequency + if ((*pObj)[0]->data.misc.bFrequency == bFrequency) + { + // SANDRO - added merc records and some exp + if ( ((*pObj)[0]->data.misc.ubBombOwner) > 1 ) + { + if ( MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->ubProfile != NO_PROFILE && + MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->bTeam == gbPlayerNum ) + { + gMercProfiles[MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)]->ubProfile].records.usExpDetonated++; + + StatChange( MercPtrs[((*pObj)[0]->data.misc.ubBombOwner - 2)], EXPLODEAMT, ( 5 ), FALSE ); + } + } + + gubPersonToSetOffExplosions = ubID; + + // ignite explosions manually - this item is not in the WorldBobms-structure, so we can't add it to the queue + IgniteExplosion( ubID, pSoldier->sX, pSoldier->sY, (INT16) (gpWorldLevelData[pSoldier->sGridNo].sHeight), pSoldier->sGridNo, pObj->usItem, pSoldier->pathing.bLevel, pSoldier->ubDirection ); + + DeleteObj( pObj ); + } + } + else // defuse bombs + { + // check for frequency + if ((*pObj)[0]->data.bDefuseFrequency == bFrequency) + { + // check for a defuse + if ( HasAttachmentOfClass(pObj, AC_DEFUSE) ) + { + (*pObj)[0]->data.bTrap = 0; + + if ( (*pObj).fFlags & OBJECT_KNOWN_TO_BE_TRAPPED ) + pObj->fFlags &= ~( OBJECT_KNOWN_TO_BE_TRAPPED ); + + if ( (*pObj).fFlags & OBJECT_ARMED_BOMB ) + pObj->fFlags &= ~( OBJECT_ARMED_BOMB ); + + // set back ubWireNetworkFlag and bDefuseFrequency, but not the direction... bomb is still aimed, it is just turned off + (*pObj)[0]->data.ubWireNetworkFlag = 0; + (*pObj)[0]->data.bDefuseFrequency = 0; + (*pObj)[0]->data.misc.bDetonatorType = 0; + (*pObj)[0]->data.misc.bDelay = 0; + (*pObj)[0]->data.misc.bFrequency = 0; + (*pObj)[0]->data.bTrap = 0; + } + } + } + } + } + } + } + } + } } void SetOffPanicBombs( UINT8 ubID, INT8 bPanicTrigger )