diff --git a/GameSettings.cpp b/GameSettings.cpp index 8516a802a..4ba69e16f 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -248,6 +248,7 @@ void UpdateFeatureFlags() gGameExternalOptions.gfAllowSnow = gGameSettings.fFeatures[FF_ALLOW_SNOW]; gGameExternalOptions.fMiniEventsEnabled = gGameSettings.fFeatures[FF_MINI_EVENTS]; gGameExternalOptions.fRebelCommandEnabled = gGameSettings.fFeatures[FF_REBEL_COMMAND]; + gGameExternalOptions.fStrategicTransportGroupsEnabled = gGameSettings.fFeatures[FF_STRATEGIC_TRANSPORT_GROUPS]; } else { @@ -497,6 +498,7 @@ BOOLEAN LoadFeatureFlags() gGameSettings.fFeatures[FF_ALLOW_SNOW] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_ALLOW_SNOW", TRUE, FALSE); gGameSettings.fFeatures[FF_MINI_EVENTS] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_MINI_EVENTS", FALSE, FALSE); gGameSettings.fFeatures[FF_REBEL_COMMAND] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_REBEL_COMMAND", FALSE, FALSE); + gGameSettings.fFeatures[FF_STRATEGIC_TRANSPORT_GROUPS] = iniReader.ReadBoolean("JA2 Feature Flags", "FF_STRATEGIC_TRANSPORT_GROUPS", FALSE, FALSE); } } catch(vfs::Exception) @@ -725,6 +727,7 @@ BOOLEAN SaveFeatureFlags() settings << "FF_ALLOW_SNOW = " << (gGameSettings.fFeatures[FF_ALLOW_SNOW] ? "TRUE" : "FALSE") << endl; settings << "FF_MINI_EVENTS = " << (gGameSettings.fFeatures[FF_MINI_EVENTS] ? "TRUE" : "FALSE") << endl; settings << "FF_REBEL_COMMAND = " << (gGameSettings.fFeatures[FF_REBEL_COMMAND] ? "TRUE" : "FALSE") << endl; + settings << "FF_STRATEGIC_TRANSPORT_GROUPS = " << (gGameSettings.fFeatures[FF_STRATEGIC_TRANSPORT_GROUPS] ? "TRUE" : "FALSE") << endl; try { @@ -2161,6 +2164,10 @@ void LoadGameExternalOptions() gGameExternalOptions.fAlternativeHelicopterFuelSystem = iniReader.ReadBoolean("Strategic Gameplay Settings","ALTERNATIVE_HELICOPTER_FUEL_SYSTEM", TRUE); gGameExternalOptions.fHelicopterPassengersCanGetHit = iniReader.ReadBoolean("Strategic Gameplay Settings","HELICOPTER_PASSENGERS_CAN_GET_HIT", TRUE); + gGameExternalOptions.fStrategicTransportGroupsDebug = iniReader.ReadBoolean("Strategic Gameplay Settings", "STRATEGIC_TRANSPORT_GROUPS_DEBUG", FALSE, FALSE); + gGameExternalOptions.fStrategicTransportGroupsEnabled = iniReader.ReadBoolean("Strategic Gameplay Settings", "STRATEGIC_TRANSPORT_GROUPS_ENABLED", FALSE); + gGameExternalOptions.iMaxSimultaneousTransportGroups = iniReader.ReadInteger("Strategic Gameplay Settings", "MAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", 5, 1, 10); + //################# Morale Settings ################## gGameExternalOptions.sMoraleModAppearance = iniReader.ReadInteger("Morale Settings","MORALE_MOD_APPEARANCE", 1, 0, 5); gGameExternalOptions.sMoraleModRefinement = iniReader.ReadInteger("Morale Settings","MORALE_MOD_REFINEMENT", 2, 0, 5); @@ -4187,6 +4194,8 @@ void LoadRebelCommandSettings() gRebelCommandSettings.iDisruptAsdDuration_Bonus_Nightops = iniReader.ReadInteger("Rebel Command Settings", "DISRUPT_ASD_DURATION_BONUS_NIGHTOPS", 48, 0, 255); gRebelCommandSettings.iDisruptAsdDuration_Bonus_Technician = iniReader.ReadInteger("Rebel Command Settings", "DISRUPT_ASD_DURATION_BONUS_TECHNICIAN", 48, 0, 255); + gRebelCommandSettings.iForgeTransportOrdersSuccessChance = iniReader.ReadInteger("Rebel Command Settings", "FORGE_TRANSPORT_ORDERS_SUCCESS_CHANCE", 50, 0, 100); + gRebelCommandSettings.iGetEnemyMovementTargetsSuccessChance = iniReader.ReadInteger("Rebel Command Settings", "STRATEGIC_INTEL_SUCCESS_CHANCE", 50, 0, 100); gRebelCommandSettings.iGetEnemyMovementTargetsDuration = iniReader.ReadInteger("Rebel Command Settings", "STRATEGIC_INTEL_DURATION", 72, 0, 255); gRebelCommandSettings.iGetEnemyMovementTargetsDuration_Bonus_Covert = iniReader.ReadInteger("Rebel Command Settings", "STRATEGIC_INTEL_DURATION_BONUS_COVERT", 48, 0, 255); diff --git a/GameSettings.h b/GameSettings.h index 8be6f7ca5..8ca4097e3 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -181,6 +181,7 @@ enum FF_ALLOW_SNOW, FF_MINI_EVENTS, FF_REBEL_COMMAND, + FF_STRATEGIC_TRANSPORT_GROUPS, NUM_FEATURE_FLAGS, }; @@ -1602,6 +1603,10 @@ typedef struct BOOLEAN fAlternativeHelicopterFuelSystem; BOOLEAN fHelicopterPassengersCanGetHit; + BOOLEAN fStrategicTransportGroupsDebug; + BOOLEAN fStrategicTransportGroupsEnabled; + INT8 iMaxSimultaneousTransportGroups; + UINT16 usHelicopterHoverCostOnGreenTile; UINT16 usHelicopterHoverCostOnRedTile; @@ -1878,6 +1883,8 @@ typedef struct UINT8 iDisruptAsdDuration_Bonus_Nightops; UINT8 iDisruptAsdDuration_Bonus_Technician; + INT8 iForgeTransportOrdersSuccessChance; + INT8 iGetEnemyMovementTargetsSuccessChance; UINT8 iGetEnemyMovementTargetsDuration; UINT8 iGetEnemyMovementTargetsDuration_Bonus_Covert; diff --git a/Laptop/history.cpp b/Laptop/history.cpp index 558029cac..6067098da 100644 --- a/Laptop/history.cpp +++ b/Laptop/history.cpp @@ -1259,6 +1259,7 @@ void ProcessHistoryTransactionString(STR16 pString, HistoryUnitPtr pHistory) case HISTORY_SLAY_MYSTERIOUSLY_LEFT: case HISTORY_WALDO: case HISTORY_HELICOPTER_REPAIR_STARTED: + case HISTORY_INTERCEPTED_TRANSPORT_GROUP: //swprintf( pString, pHistoryStrings[ pHistory->ubCode ], pHistory->ubSecondCode ); swprintf( pString, HistoryName[ pHistory->ubCode ].sHistory, pHistory->ubSecondCode ); break; diff --git a/Laptop/history.h b/Laptop/history.h index 6fecf8d2e..5a0a3d63d 100644 --- a/Laptop/history.h +++ b/Laptop/history.h @@ -113,6 +113,7 @@ enum{ HISTORY_MERC_KILLED_CHARACTER, HISTORY_WALDO, HISTORY_HELICOPTER_REPAIR_STARTED, + HISTORY_INTERCEPTED_TRANSPORT_GROUP, TEXT_NUM_HISTORY }; diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index c602e12c7..04fa2d561 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -633,6 +633,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve1"); switch( GetEnemyEncounterCode() ) { case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: gpAR->ubPlayerDefenceAdvantage = 21; //Skewed to the player's advantage for convenience purposes. break; case ENEMY_INVASION_CODE: @@ -1745,6 +1746,7 @@ void RenderAutoResolve() swprintf( str, gpStrategicString[STR_AR_ATTACK_HEADER] ); break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: swprintf( str, gpStrategicString[STR_AR_ENCOUNTER_HEADER] ); break; case ENEMY_INVASION_CODE: @@ -6058,6 +6060,5 @@ BOOLEAN IndividualMilitiaInUse_AutoResolve( UINT32 aMilitiaId ) } } } - return FALSE; -} \ No newline at end of file +} diff --git a/Strategic/CMakeLists.txt b/Strategic/CMakeLists.txt index 8e15ba341..295ff41a1 100644 --- a/Strategic/CMakeLists.txt +++ b/Strategic/CMakeLists.txt @@ -47,6 +47,7 @@ set(StrategicSrc "${CMAKE_CURRENT_SOURCE_DIR}/Strategic Status.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Strategic Town Loyalty.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/strategic town reputation.cpp" +"${CMAKE_CURRENT_SOURCE_DIR}/Strategic Transport Groups.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Strategic Turns.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/strategic.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/strategicmap.cpp" diff --git a/Strategic/Game Event Hook.cpp b/Strategic/Game Event Hook.cpp index 6c2ea4287..6a64799af 100644 --- a/Strategic/Game Event Hook.cpp +++ b/Strategic/Game Event Hook.cpp @@ -48,6 +48,7 @@ #include "LuaInitNPCs.h" // added by Flugente #include "MiniEvents.h" #include "Rebel Command.h" + #include "interface Dialogue.h" #include "connect.h" @@ -668,6 +669,11 @@ BOOLEAN ExecuteStrategicEvent( STRATEGICEVENT *pEvent ) case EVENT_REBELCOMMAND: RebelCommand::HandleStrategicEvent(pEvent->uiParam); break; + + case EVENT_RETURN_TRANSPORT_GROUP: + // for this action, we only care about the groupid, which is in the event param + ExecuteStrategicAIAction(NPC_ACTION_RETURN_TRANSPORT_GROUP, 0, 0, pEvent->uiParam); + break; } gfPreventDeletionOfAnyEvent = fOrigPreventFlag; return TRUE; diff --git a/Strategic/Game Event Hook.h b/Strategic/Game Event Hook.h index 29e08bb59..fa9cacd98 100644 --- a/Strategic/Game Event Hook.h +++ b/Strategic/Game Event Hook.h @@ -154,6 +154,9 @@ enum EVENT_REBELCOMMAND, + EVENT_RETURN_TRANSPORT_GROUP, + EVENT_TRANSPORT_GROUP_DEFEATED, + NUMBER_OF_EVENT_TYPES_PLUS_ONE, NUMBER_OF_EVENT_TYPES = NUMBER_OF_EVENT_TYPES_PLUS_ONE - 1 }; @@ -217,4 +220,4 @@ void DeleteAllStrategicEvents(); // Flugente: return vector of all events of type ubCallbackID with time and param std::vector< std::pair > GetAllStrategicEventsOfType( UINT8 ubCallbackID ); -#endif \ No newline at end of file +#endif diff --git a/Strategic/Game Events.cpp b/Strategic/Game Events.cpp index 48ebbb294..d6fc6d9bb 100644 --- a/Strategic/Game Events.cpp +++ b/Strategic/Game Events.cpp @@ -137,6 +137,7 @@ CHAR16 gEventName[NUMBER_OF_EVENT_TYPES_PLUS_ONE][40]={ L"ArmyFinishTraining", L"MiniEvent", L"ARC_Event", + L"ReturnTransportGroup", }; #endif diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index cfa3d83b9..b709c3a02 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -46,6 +46,7 @@ #include "LuaInitNPCs.h" // added by Flugente #include "Game Event Hook.h" // added by Flugente #include "Rebel Command.h" + #include "Strategic Transport Groups.h" #include "Quests.h" #include "connect.h" @@ -841,6 +842,8 @@ void fillMapColoursForVisitedSectors(INT32(&colorMap)[ MAXIMUM_VALID_Y_COORDINAT } } + FillMapColoursForTransportGroups(colorMap); + if (RebelCommand::ShowEnemyMovementTargets()) { const auto targetColor = MAP_SHADE_LT_RED; @@ -8584,6 +8587,25 @@ void DetermineMapIntelData( INT32 asSectorZ ) } } + // transport groups + std::map map = GetTransportGroupSectorInfo(); + for (const auto iter : map) + { + CHAR16 str[128]; + + switch (iter.second) + { + case TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedGroup: + swprintf( str, gpStrategicString[STR_PB_TRANSPORT_GROUP]); + break; + + case TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedDestination: + swprintf( str, gpStrategicString[STR_PB_TRANSPORT_GROUP_EN_ROUTE]); + break; + } + AddIntelAndQuestMapDataForSector( SECTORX(iter.first), SECTORY(iter.first), MAP_SHADE_LT_YELLOW, -1, str, L"" ); + } + // uncovered terrorists we know of for ( int cnt = 0; cnt < 6; ++cnt ) { diff --git a/Strategic/PreBattle Interface.cpp b/Strategic/PreBattle Interface.cpp index 81909888a..f05cf42ed 100644 --- a/Strategic/PreBattle Interface.cpp +++ b/Strategic/PreBattle Interface.cpp @@ -47,6 +47,7 @@ #include "CampaignStats.h" // added by Flugente #include "militiasquads.h" // added by Flugente #include "SkillCheck.h" // added by Flugente + #include "Strategic Transport Groups.h" #ifdef JA2UB #include "ub_config.h" @@ -438,7 +439,7 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) } else if ( pBattleGroup && pBattleGroup->usGroupTeam != OUR_TEAM && NumNonPlayerTeamMembersInSector( pBattleGroup->ubSectorX, pBattleGroup->ubSectorY, MILITIA_TEAM ) > 0 ) { - SetEnemyEncounterCode( ENEMY_ENCOUNTER_CODE ); + SetEnemyEncounterCode( pBattleGroup->usGroupTeam == ENEMY_TEAM && pBattleGroup->pEnemyGroup->ubIntention == TRANSPORT ? TRANSPORT_INTERCEPT_CODE : ENEMY_ENCOUNTER_CODE ); } else if( GetEnemyEncounterCode() == ENTERING_ENEMY_SECTOR_CODE || GetEnemyEncounterCode() == ENEMY_ENCOUNTER_CODE || @@ -452,7 +453,8 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) GetEnemyEncounterCode() == CONCEALINSERTION_CODE || GetEnemyEncounterCode() == BLOODCAT_ATTACK_CODE || GetEnemyEncounterCode() == ZOMBIE_ATTACK_CODE || - GetEnemyEncounterCode() == BANDIT_ATTACK_CODE ) + GetEnemyEncounterCode() == BANDIT_ATTACK_CODE || + GetEnemyEncounterCode() == TRANSPORT_INTERCEPT_CODE ) { //use same code SetExplicitEnemyEncounterCode( GetEnemyEncounterCode() ); @@ -679,8 +681,24 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) { SetEnemyEncounterCode( ENEMY_ENCOUNTER_CODE ); + GROUP* pGroup = gpGroupList; + BOOLEAN encounteredTransportGroup = FALSE; + while (pGroup) + { + if (pGroup->usGroupTeam == ENEMY_TEAM && pGroup->pEnemyGroup->ubIntention == TRANSPORT && pGroup->ubSectorX == gpBattleGroup->ubSectorX && pGroup->ubSectorY == gpBattleGroup->ubSectorY && pGroup->ubSectorZ == gpBattleGroup->ubSectorZ) + { + encounteredTransportGroup = TRUE; + break; + } + + pGroup = pGroup->next; + } + if (encounteredTransportGroup) + { + SetEnemyEncounterCode( TRANSPORT_INTERCEPT_CODE ); + } // Flugente: no ambushes on an airdrop - if ( !fAirDrop ) + else if ( !fAirDrop ) { //Don't consider ambushes until the player has reached 25% (normal) progress if( gfHighPotentialForAmbush ) @@ -798,7 +816,9 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) } else { //Are enemies invading a town, or just encountered the player. - if( GetTownIdForSector( gubPBSectorX, gubPBSectorY ) ) + if (pBattleGroup && pBattleGroup->usGroupTeam == ENEMY_TEAM && pBattleGroup->pEnemyGroup->ubIntention == TRANSPORT) + SetEnemyEncounterCode( TRANSPORT_INTERCEPT_CODE ); + else if( GetTownIdForSector( gubPBSectorX, gubPBSectorY ) ) SetEnemyEncounterCode( ENEMY_INVASION_CODE ); //SAM sites not in towns will also be considered to be important else if( pSector->uiFlags & SF_SAM_SITE ) @@ -866,7 +886,7 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) } HideButton( giMapContractButton ); - if( GetEnemyEncounterCode() == ENEMY_ENCOUNTER_CODE ) + if( GetEnemyEncounterCode() == ENEMY_ENCOUNTER_CODE || GetEnemyEncounterCode() == TRANSPORT_INTERCEPT_CODE ) { //we know how many enemies are here, so until we leave the sector, we will continue to display the value. //the flag will get cleared when time advances after the fEnemyInSector flag is clear. @@ -955,6 +975,7 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) { case CREATURE_ATTACK_CODE: case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: case ENEMY_INVASION_CODE: case ENEMY_INVASION_AIRDROP_CODE: case BLOODCAT_ATTACK_CODE: @@ -1213,6 +1234,7 @@ void RenderPBHeader( INT32 *piX, INT32 *piWidth) swprintf( str, gpStrategicString[ STR_PB_ENEMYINVASION_HEADER ] ); break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: swprintf( str, gpStrategicString[ STR_PB_ENEMYENCOUNTER_HEADER ] ); break; case ENEMY_AMBUSH_CODE: @@ -2520,6 +2542,10 @@ void LogBattleResults( UINT8 ubVictoryCode) break; case CONCEALINSERTION_CODE: break; + case TRANSPORT_INTERCEPT_CODE: + AddHistoryToPlayersLog( HISTORY_INTERCEPTED_TRANSPORT_GROUP, 0, GetWorldTotalMin(), sSectorX, sSectorY ); + NotifyTransportGroupDefeated(); + break; } // Flugente: campaign stats @@ -2534,6 +2560,7 @@ void LogBattleResults( UINT8 ubVictoryCode) AddHistoryToPlayersLog( HISTORY_LOSTTOWNSECTOR, 0, GetWorldTotalMin(), sSectorX, sSectorY ); break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: AddHistoryToPlayersLog( HISTORY_LOSTBATTLE, 0, GetWorldTotalMin(), sSectorX, sSectorY ); break; case ENEMY_AMBUSH_CODE: @@ -2567,6 +2594,7 @@ void LogBattleResults( UINT8 ubVictoryCode) gCurrentIncident.usIncidentFlags |= INCIDENT_ATTACK_ENEMY; break; case ENEMY_ENCOUNTER_CODE: + case TRANSPORT_INTERCEPT_CODE: case ENTERING_ENEMY_SECTOR_CODE: case CREATURE_ATTACK_CODE: case ENTERING_BLOODCAT_LAIR_CODE: diff --git a/Strategic/PreBattle Interface.h b/Strategic/PreBattle Interface.h index d8bc045e3..78070486e 100644 --- a/Strategic/PreBattle Interface.h +++ b/Strategic/PreBattle Interface.h @@ -47,6 +47,8 @@ enum BLOODCAT_ATTACK_CODE, // Flugente: like CREATURE_ATTACK_CODE, but with cats ZOMBIE_ATTACK_CODE, // Flugente: like CREATURE_ATTACK_CODE, but with zombies BANDIT_ATTACK_CODE, // Flugente: like CREATURE_ATTACK_CODE, but with bandits + + TRANSPORT_INTERCEPT_CODE, // rftr: like ENEMY_ENCOUNTER_CODE }; extern BOOLEAN gfAutoAmbush; @@ -112,4 +114,4 @@ enum }; void LogBattleResults( UINT8 ubVictoryCode); -#endif \ No newline at end of file +#endif diff --git a/Strategic/Queen Command.cpp b/Strategic/Queen Command.cpp index b964eed51..074ef6c32 100644 --- a/Strategic/Queen Command.cpp +++ b/Strategic/Queen Command.cpp @@ -42,6 +42,7 @@ #include "CampaignStats.h" // added by Flugente #include "ASD.h" // added by Flugente #include "Interface Panels.h" + #include "Strategic Transport Groups.h" #ifdef JA2BETAVERSION extern BOOLEAN gfClearCreatureQuest; @@ -98,7 +99,6 @@ void HandleBloodCatDeaths( SECTORINFO *pSector ); extern void Ensure_RepairedGarrisonGroup( GARRISON_GROUP **ppGarrison, INT32 *pGarraySize ); - void ValidateEnemiesHaveWeapons() { #ifdef JA2BETAVERSION @@ -613,6 +613,9 @@ BOOLEAN PrepareEnemyForSectorBattle() gfPendingNonPlayerTeam[ENEMY_TEAM] = FALSE; + // rftr: clear cached transport groups + ClearTransportGroupMap(); + if( gbWorldSectorZ > 0 ) return PrepareEnemyForUndergroundBattle(); @@ -640,9 +643,9 @@ BOOLEAN PrepareEnemyForSectorBattle() for( unsigned ubIndex = 0; ubIndex < ubDirNumber; ++ubIndex ) { - while ( NumMobileEnemiesInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ) ) && GetNonPlayerGroupInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ) ) + while ( NumMobileEnemiesInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ) ) && GetNonPlayerGroupInSectorForReinforcement( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ) ) { - pGroup = GetNonPlayerGroupInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ); + pGroup = GetNonPlayerGroupInSectorForReinforcement( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), ENEMY_TEAM ); pGroup->ubPrevX = pGroup->ubSectorX; pGroup->ubPrevY = pGroup->ubSectorY; @@ -669,10 +672,23 @@ BOOLEAN PrepareEnemyForSectorBattle() HandleArrivalOfReinforcements( pGroup ); } + // for transport groups, track how many enemies of each type we're adding so we can update drops for them + if (pGroup->usGroupTeam == ENEMY_TEAM && pGroup->pEnemyGroup->ubIntention == TRANSPORT && pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && !gbWorldSectorZ) + { + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ADMINISTRATOR, pGroup->pEnemyGroup->ubNumAdmins); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ARMY, pGroup->pEnemyGroup->ubNumTroops); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ELITE, pGroup->pEnemyGroup->ubNumElites); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ROBOT, pGroup->pEnemyGroup->ubNumRobots); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_JEEP, pGroup->pEnemyGroup->ubNumJeeps); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_TANK, pGroup->pEnemyGroup->ubNumTanks); + } + pGroup = pGroup->next; } } + UpdateTransportGroupInventory(); + ValidateEnemiesHaveWeapons(); UnPauseGame(); return ( ( BOOLEAN) ( gpBattleGroup->ubGroupSize > 0 ) ); @@ -852,6 +868,7 @@ BOOLEAN PrepareEnemyForSectorBattle() if ( pGroup->usGroupTeam == ENEMY_TEAM && !pGroup->fVehicle && pGroup->ubSectorX == gWorldSectorX && pGroup->ubSectorY == gWorldSectorY && !gbWorldSectorZ ) { //Process enemy group in sector. + const BOOLEAN isTransportGroup = pGroup->pEnemyGroup->ubIntention == TRANSPORT; if( sNumSlots > 0 ) { AssertGE(pGroup->pEnemyGroup->ubNumAdmins, pGroup->pEnemyGroup->ubAdminsInBattle); @@ -865,6 +882,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubAdminsInBattle += ubNumAdmins; ubTotalAdmins += ubNumAdmins; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ADMINISTRATOR, ubNumAdmins); } if( sNumSlots > 0 ) { //Add regular army forces. @@ -879,6 +899,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubTroopsInBattle += ubNumTroops; ubTotalTroops += ubNumTroops; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ARMY, ubNumTroops); } if( sNumSlots > 0 ) { //Add elite troops @@ -893,6 +916,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubElitesInBattle += ubNumElites; ubTotalElites += ubNumElites; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ELITE, ubNumElites); } if( sNumSlots > 0 ) { //Add robots @@ -907,6 +933,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubRobotsInBattle += ubNumRobots; ubTotalRobots += ubNumRobots; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ROBOT, ubNumRobots); } if( sNumSlots > 0 ) { //Add tanks @@ -921,6 +950,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubTanksInBattle += ubNumTanks; ubTotalTanks += ubNumTanks; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_TANK, ubNumTanks); } if ( sNumSlots > 0 ) { @@ -936,6 +968,9 @@ BOOLEAN PrepareEnemyForSectorBattle() } pGroup->pEnemyGroup->ubJeepsInBattle += ubNumJeeps; ubTotalJeeps += ubNumJeeps; + + if (isTransportGroup) + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_JEEP, ubNumJeeps); } //NOTE: //no provisions for profile troop leader or retreat groups yet. @@ -976,6 +1011,7 @@ BOOLEAN PrepareEnemyForSectorBattle() unsigned firstSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; unsigned lastSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; unsigned slotsAvailable = lastSlot-firstSlot+1; + while( pGroup && sNumSlots > 0 ) { if ( pGroup->usGroupTeam != OUR_TEAM && !pGroup->fVehicle && @@ -1088,6 +1124,7 @@ BOOLEAN PrepareEnemyForSectorBattle() } break; } + } // Flugente: instead of just crashing the game without any explanation to the user, ignore this issue if it still exists. @@ -1103,6 +1140,8 @@ BOOLEAN PrepareEnemyForSectorBattle() pGroup = pGroup->next; } + UpdateTransportGroupInventory(); + ValidateEnemiesHaveWeapons(); UnPauseGame(); @@ -2145,6 +2184,16 @@ void AddEnemiesToBattle( GROUP *pGroup, UINT8 ubStrategicInsertionCode, UINT8 ub UINT8 ubTotalSoldiers; UINT8 bDesiredDirection=0; + // while transport groups can't normally reinforce, this covers the case where a transport group enters a sector (via normal movement) + // where a battle is in progress. + if (pGroup && pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ADMINISTRATOR, ubNumAdmins); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ARMY, ubNumTroops); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_ELITE, ubNumElites); + AddToTransportGroupMap(pGroup->ubGroupID, SOLDIER_CLASS_JEEP, ubNumJeeps); + } + switch( ubStrategicInsertionCode ) { case INSERTION_CODE_NORTH: bDesiredDirection = SOUTHEAST; break; @@ -3594,3 +3643,5 @@ void CorrectTurncoatCount( INT16 sSectorX, INT16 sSectorY ) pGroup = pGroup->next; } } + + diff --git a/Strategic/Rebel Command.cpp b/Strategic/Rebel Command.cpp index f7d257ec8..b5ce30aa0 100644 --- a/Strategic/Rebel Command.cpp +++ b/Strategic/Rebel Command.cpp @@ -41,8 +41,10 @@ How to add a new admin action: - if effect applies outside of towns, add help text range band as appropriate to SetupAdminActionBox How to add a new mission: -- add to the RebelCommandAgentMissions enum in the header -- add strings to text files (szRebelCommandAgentMissionsText) +- add strings to text files (szRebelCommandText (trait bonuses), szRebelCommandAgentMissionsText (title/description)) +- add to the RebelCommandText and RebelCommandAgentMissions enums in the header +- add to the RebelCommandAgentMissionsText enums in the cpp +- add mission variables to GameSettings - add values to MissionHelpers::missionInfo table in SetupInfo() - add to valid check in HandleStrategicEvent() (allows advance from first event/prepare to second event/active effect) - add to SetupMissionAgentBox() (mission description and merc bonus text) @@ -91,6 +93,7 @@ Points of interest: #include "Strategic Mines.h" #include "Strategic Movement.h" #include "Strategic Town Loyalty.h" +#include "Strategic Transport Groups.h" #include "Structure Wrap.h" #include "Tactical Save.h" #include "Text.h" @@ -405,6 +408,7 @@ enum RebelCommandAgentMissionsText // keep this synced with szRebelCommandAgentM { MISSION_TEXT(DEEP_DEPLOYMENT) MISSION_TEXT(DISRUPT_ASD) + MISSION_TEXT(FORGE_TRANSPORT_ORDERS) MISSION_TEXT(GET_ENEMY_MOVEMENT_TARGETS) MISSION_TEXT(IMPROVE_LOCAL_SHOPS) MISSION_TEXT(REDUCE_STRATEGIC_DECISION_SPEED) @@ -2221,6 +2225,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DEEP_DEPLOYMENT_TITLE]); break; case RCAM_DISRUPT_ASD: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DISRUPT_ASD_TITLE]); break; + case RCAM_FORGE_TRANSPORT_ORDERS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_FORGE_TRANSPORT_ORDERS_TITLE]); break; case RCAM_GET_ENEMY_MOVEMENT_TARGETS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_GET_ENEMY_MOVEMENT_TARGETS_TITLE]); break; case RCAM_IMPROVE_LOCAL_SHOPS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_IMPROVE_LOCAL_SHOPS_TITLE]); break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_REDUCE_STRATEGIC_DECISION_SPEED_TITLE]); break; @@ -2241,6 +2246,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: missionDurationBase = gRebelCommandSettings.iDeepDeploymentDuration; break; case RCAM_DISRUPT_ASD: missionDurationBase = gRebelCommandSettings.iDisruptAsdDuration; break; + case RCAM_FORGE_TRANSPORT_ORDERS: missionDurationBase = 1; break; // instant effect case RCAM_GET_ENEMY_MOVEMENT_TARGETS: missionDurationBase = gRebelCommandSettings.iGetEnemyMovementTargetsDuration; break; case RCAM_IMPROVE_LOCAL_SHOPS: missionDurationBase = gRebelCommandSettings.iImproveLocalShopsDuration; break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: missionDurationBase = gRebelCommandSettings.iReduceStrategicDecisionSpeedDuration; break; @@ -2264,6 +2270,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: missionSuccessChanceBase = gRebelCommandSettings.iDeepDeploymentSuccessChance; break; case RCAM_DISRUPT_ASD: missionSuccessChanceBase = gRebelCommandSettings.iDisruptAsdSuccessChance; break; + case RCAM_FORGE_TRANSPORT_ORDERS: missionSuccessChanceBase = gRebelCommandSettings.iForgeTransportOrdersSuccessChance; break; case RCAM_GET_ENEMY_MOVEMENT_TARGETS: missionSuccessChanceBase = gRebelCommandSettings.iGetEnemyMovementTargetsSuccessChance; break; case RCAM_IMPROVE_LOCAL_SHOPS: missionSuccessChanceBase = gRebelCommandSettings.iImproveLocalShopsSuccessChance; break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: missionSuccessChanceBase = gRebelCommandSettings.iReduceStrategicDecisionSpeedSuccessChance; break; @@ -2284,6 +2291,7 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) { case RCAM_DEEP_DEPLOYMENT: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DEEP_DEPLOYMENT_DESC]); break; case RCAM_DISRUPT_ASD: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_DISRUPT_ASD_DESC]); break; + case RCAM_FORGE_TRANSPORT_ORDERS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_FORGE_TRANSPORT_ORDERS_DESC]); break; case RCAM_GET_ENEMY_MOVEMENT_TARGETS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_GET_ENEMY_MOVEMENT_TARGETS_DESC]); break; case RCAM_IMPROVE_LOCAL_SHOPS: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_IMPROVE_LOCAL_SHOPS_DESC]); break; case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: swprintf(sText, szRebelCommandAgentMissionsText[RCAMT_REDUCE_STRATEGIC_DECISION_SPEED_DESC]); break; @@ -2454,6 +2462,12 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) } } + case RCAM_FORGE_TRANSPORT_ORDERS: + { + // no special modifiers. included for completeness. + } + break; + case RCAM_GET_ENEMY_MOVEMENT_TARGETS: { // no special modifiers. included for completeness. @@ -2610,7 +2624,13 @@ BOOLEAN SetupMissionAgentBox(UINT16 x, UINT16 y, INT8 index) swprintf(sText, szRebelCommandText[RCT_MISSION_CANT_START_CONTRACT_EXPIRING]); } } - else if (agentIndex[index] == mercs.size() && rebelCommandSaveInfo.availableMissions[index] == RCAM_SEND_SUPPLIES_TO_TOWN) + else if (agentIndex[index] == mercs.size() && + ( + rebelCommandSaveInfo.availableMissions[index] == RCAM_SEND_SUPPLIES_TO_TOWN || + rebelCommandSaveInfo.availableMissions[index] == RCAM_FORGE_TRANSPORT_ORDERS + ) + + ) { canStartMission = FALSE; swprintf(sText, szRebelCommandText[RCT_MISSION_CANT_USE_REBEL_AGENT]); @@ -2847,6 +2867,14 @@ void PrepareMission(INT8 index) } break; + case RCAM_FORGE_TRANSPORT_ORDERS: + { + missionTitle = RCAMT_FORGE_TRANSPORT_ORDERS_TITLE; + missionSuccessChance = gRebelCommandSettings.iForgeTransportOrdersSuccessChance; + missionDuration = 12; + } + break; + case RCAM_GET_ENEMY_MOVEMENT_TARGETS: { missionTitle = RCAMT_GET_ENEMY_MOVEMENT_TARGETS_TITLE; @@ -3897,6 +3925,7 @@ void DailyUpdate() { if (i == RCAM_SOLDIER_BOUNTIES_KINGPIN && !(CheckFact(FACT_KINGPIN_INTRODUCED_SELF, 0) == TRUE && CheckFact(FACT_KINGPIN_DEAD, 0) == FALSE && CheckFact(FACT_KINGPIN_IS_ENEMY, 0) == FALSE && CurrentPlayerProgressPercentage() >= 30)) continue; else if (i == RCAM_DISRUPT_ASD && gGameExternalOptions.fASDActive == FALSE) continue; + else if (i == RCAM_FORGE_TRANSPORT_ORDERS && gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) continue; validMissions.insert(static_cast(i)); } @@ -4299,6 +4328,16 @@ void SetupInfo() {0, 0, 0, 0}, {0, MissionHelpers::DISRUPT_ASD_STEAL_FUEL, MissionHelpers::DISRUPT_ASD_DESTROY_RESERVES, 0} }); + //RCAM_FORGE_TRANSPORT_ORDERS + MissionHelpers::missionInfo.push_back( + { + {COVERT_NT }, + {-1}, + {0}, + {0.f}, + {0}, + {0} + }); //RCAM_GET_ENEMY_MOVEMENT_TARGETS MissionHelpers::missionInfo.push_back( { @@ -4902,6 +4941,7 @@ void HandleStrategicEvent(const UINT32 eventParam) { case RCAM_DEEP_DEPLOYMENT: case RCAM_DISRUPT_ASD: + case RCAM_FORGE_TRANSPORT_ORDERS: case RCAM_GET_ENEMY_MOVEMENT_TARGETS: case RCAM_IMPROVE_LOCAL_SHOPS: case RCAM_REDUCE_STRATEGIC_DECISION_SPEED: @@ -4926,7 +4966,15 @@ void HandleStrategicEvent(const UINT32 eventParam) if (validMission) { const UINT32 activatedMissionParam = SerialiseMissionSecondEvent(evt1.sentGenericRebelAgent, evt1.mercProfileId, mission, extraBits); - AddStrategicEvent(EVENT_REBELCOMMAND, GetWorldTotalMin() + 60 * evt1.missionDurationInHours, activatedMissionParam); + if (mission == RCAM_FORGE_TRANSPORT_ORDERS) + { + // don't send a follow-up event for instant-result missions + } + else + { + AddStrategicEvent(EVENT_REBELCOMMAND, GetWorldTotalMin() + 60 * evt1.missionDurationInHours, activatedMissionParam); + missionMap.insert(std::make_pair(mission, activatedMissionParam)); + } if (!evt1.sentGenericRebelAgent) { @@ -4935,6 +4983,11 @@ void HandleStrategicEvent(const UINT32 eventParam) SOLDIERTYPE* pSoldier = MercPtrs[i]; if (pSoldier->ubProfile == evt1.mercProfileId) { + if (mission == RCAM_FORGE_TRANSPORT_ORDERS) + { + ForceDeployTransportGroup(SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)); + } + // mission successful! give some experience pts StatChange(pSoldier, LDRAMT, 20, FROM_SUCCESS); StatChange(pSoldier, WISDOMAMT, 15, FROM_SUCCESS); @@ -4943,7 +4996,6 @@ void HandleStrategicEvent(const UINT32 eventParam) } } - missionMap.insert(std::make_pair(mission, activatedMissionParam)); swprintf(msgBoxText, szRebelCommandText[RCT_MISSION_SUCCESS], szRebelCommandAgentMissionsText[evt1.missionId * 2]); swprintf(screenMsgText, szRebelCommandText[RCT_MISSION_SUCCESS], szRebelCommandAgentMissionsText[evt1.missionId * 2]); ScreenMsg(FONT_MCOLOR_LTGREEN, MSG_INTERFACE, screenMsgText); diff --git a/Strategic/Rebel Command.h b/Strategic/Rebel Command.h index f8c9bc241..22b6fd3b6 100644 --- a/Strategic/Rebel Command.h +++ b/Strategic/Rebel Command.h @@ -78,6 +78,7 @@ enum RebelCommandAgentMissions RCAM_NONE = -1, RCAM_DEEP_DEPLOYMENT = 0, RCAM_DISRUPT_ASD, // only available if ASD enabled + RCAM_FORGE_TRANSPORT_ORDERS, RCAM_GET_ENEMY_MOVEMENT_TARGETS, // aka Strategic Intel RCAM_IMPROVE_LOCAL_SHOPS, RCAM_REDUCE_STRATEGIC_DECISION_SPEED, // aka Slower Strategic Decisions diff --git a/Strategic/Reinforcement.cpp b/Strategic/Reinforcement.cpp index c5077b8e0..2adaa4f90 100644 --- a/Strategic/Reinforcement.cpp +++ b/Strategic/Reinforcement.cpp @@ -246,13 +246,13 @@ BOOLEAN ARMoveBestMilitiaManFromAdjacentSector(INT16 sMapX, INT16 sMapY) return TRUE; } -GROUP* GetNonPlayerGroupInSector( INT16 sMapX, INT16 sMapY, UINT8 usTeam ) +GROUP* GetNonPlayerGroupInSectorForReinforcement( INT16 sMapX, INT16 sMapY, UINT8 usTeam ) { GROUP *curr; curr = gpGroupList; while( curr ) { - if ( curr->ubSectorX == sMapX && curr->ubSectorY == sMapY && curr->usGroupTeam == usTeam && curr->ubGroupID ) + if ( curr->ubSectorX == sMapX && curr->ubSectorY == sMapY && curr->usGroupTeam == usTeam && curr->ubGroupID && (curr->usGroupTeam != ENEMY_TEAM || curr->pEnemyGroup->ubIntention != TRANSPORT) ) return curr; curr = curr->next; } @@ -280,7 +280,7 @@ UINT8 DoReinforcementAsPendingNonPlayer( INT16 sMapX, INT16 sMapY, UINT8 usTeam for( ubIndex = 0; ubIndex < ubDirNumber; ++ubIndex ) { - while ( (pGroup = GetNonPlayerGroupInSector( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), usTeam )) != NULL ) + while ( (pGroup = GetNonPlayerGroupInSectorForReinforcement( SECTORX( pusMoveDir[ubIndex][0] ), SECTORY( pusMoveDir[ubIndex][0] ), usTeam )) != NULL ) { pGroup->ubPrevX = pGroup->ubSectorX; pGroup->ubPrevY = pGroup->ubSectorY; diff --git a/Strategic/Reinforcement.h b/Strategic/Reinforcement.h index 543ff0415..e6017b51d 100644 --- a/Strategic/Reinforcement.h +++ b/Strategic/Reinforcement.h @@ -12,6 +12,6 @@ UINT8 NumEnemiesInFiveSectors( INT16 sMapX, INT16 sMapY ); //For Tactical UINT8 DoReinforcementAsPendingNonPlayer( INT16 sMapX, INT16 sMapY, UINT8 usTeam ); void AddPossiblePendingMilitiaToBattle(); -GROUP* GetNonPlayerGroupInSector( INT16 sMapX, INT16 sMapY, UINT8 usTeam ); +GROUP* GetNonPlayerGroupInSectorForReinforcement( INT16 sMapX, INT16 sMapY, UINT8 usTeam ); #endif \ No newline at end of file diff --git a/Strategic/Strategic AI.cpp b/Strategic/Strategic AI.cpp index 569dfec6c..c64d85ed1 100644 --- a/Strategic/Strategic AI.cpp +++ b/Strategic/Strategic AI.cpp @@ -33,6 +33,9 @@ #include "interface dialogue.h" #include "ASD.h" // added by Flugente #include "Rebel Command.h" + #include "Game Event Hook.h" + #include "Strategic Town Loyalty.h" + #include "Strategic Transport Groups.h" #include "GameInitOptionsScreen.h" @@ -488,7 +491,6 @@ extern INT16 sWorldSectorLocationOfFirstBattle; void ReassignAIGroup( GROUP **pGroup ); void TransferGroupToPool( GROUP **pGroup ); -void SendGroupToPool( GROUP **pGroup ); //Simply orders all garrisons to take troops from the patrol groups and send the closest troops from them. Any garrison, //whom there request isn't fulfilled (due to lack of troops), will recieve their reinforcements from the queen (P3). @@ -2418,6 +2420,11 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Strategic5"); } } } + else if (pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + ProcessTransportGroupReachedDestination(pGroup); + return TRUE; + } else { //This is a floating group at his final destination... if( pGroup->pEnemyGroup->ubIntention != STAGING && pGroup->pEnemyGroup->ubIntention != REINFORCEMENTS ) @@ -3458,7 +3465,7 @@ void EvaluateQueenSituation() dEnemyGeneralsSpeedupFactor *= RebelCommand::GetStrategicDecisionSpeedModifier(); uiOffset += dEnemyGeneralsSpeedupFactor * (zDiffSetting[gGameOptions.ubDifficultyLevel].iBaseDelayInMinutesBetweenEvaluations + Random( zDiffSetting[gGameOptions.ubDifficultyLevel].iEvaluationDelayVariance )); - + // Check/update reinforcements pool if old behavior is enabled if ( !gfUnlimitedTroops && zDiffSetting[gGameOptions.ubDifficultyLevel].iQueenPoolIncrementDaysPerDifficultyLevel == 0 ) { @@ -3488,88 +3495,88 @@ void EvaluateQueenSituation() // Gradually promote any remaining admins into troops UpgradeAdminsToTroops(); - if( ( giRequestPoints <= 0 ) || ( ( giReinforcementPoints <= 0 ) && ( giReinforcementPool <= 0 ) ) ) - { //we either have no reinforcements or request for reinforcements. - return; - } + // consider deploying a transport group + ExecuteStrategicAIAction( NPC_ACTION_DEPLOY_TRANSPORT_GROUP, 0, 0 ); - // anv: only consider garrisons and patrols that can be reinforced - // otherwise unreinforcable groups will stall the rest, effectively breaking entire system + // we either have reinforcements or a request for reinforcements + if (giRequestPoints > 0 && (giReinforcementPoints > 0 || giReinforcementPool > 0)) + { + // anv: only consider garrisons and patrols that can be reinforced + // otherwise unreinforcable groups will stall the rest, effectively breaking entire system - Ensure_RepairedGarrisonGroup( &gGarrisonGroup, &giGarrisonArraySize ); /* added NULL fix, 2007-03-03, Sgt. Kolja */ + Ensure_RepairedGarrisonGroup( &gGarrisonGroup, &giGarrisonArraySize ); /* added NULL fix, 2007-03-03, Sgt. Kolja */ - for( i = 0; i < giGarrisonArraySize; i++ ) - { - RecalculateGarrisonWeight( i ); - iWeight = gGarrisonGroup[ i ].bWeight; - if( iWeight > 0 ) + for( i = 0; i < giGarrisonArraySize; i++ ) { - if( !gGarrisonGroup[ i ].ubPendingGroupID && - EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) && - GarrisonRequestingMinimumReinforcements( i ) ) + RecalculateGarrisonWeight( i ); + iWeight = gGarrisonGroup[ i ].bWeight; + if( iWeight > 0 ) { - if( ReinforcementsApproved( i, &usDefencePoints ) ) + if( !gGarrisonGroup[ i ].ubPendingGroupID && + EnemyPermittedToAttackSector( NULL, gGarrisonGroup[ i ].ubSectorID ) && + GarrisonRequestingMinimumReinforcements( i ) ) { - iApplicableGarrisonIds[iApplicableGarrisons] = i; - iApplicableGarrisons++; - iApplicableRequestPoints += gGarrisonGroup[ i ].bWeight; + if( ReinforcementsApproved( i, &usDefencePoints ) ) + { + iApplicableGarrisonIds[iApplicableGarrisons] = i; + iApplicableGarrisons++; + iApplicableRequestPoints += gGarrisonGroup[ i ].bWeight; + } } } } - } - for( i = 0; i < giPatrolArraySize; i++ ) - { - RecalculatePatrolWeight( i ); - iWeight = gPatrolGroup[ i ].bWeight; - if( iWeight > 0 ) + for( i = 0; i < giPatrolArraySize; i++ ) { - if( !gPatrolGroup[ i ].ubPendingGroupID && PatrolRequestingMinimumReinforcements( i ) ) + RecalculatePatrolWeight( i ); + iWeight = gPatrolGroup[ i ].bWeight; + if( iWeight > 0 ) { - iApplicablePatrolIds[iApplicablePatrols] = i; - iApplicablePatrols++; - iApplicableRequestPoints += gPatrolGroup[ i ].bWeight; + if( !gPatrolGroup[ i ].ubPendingGroupID && PatrolRequestingMinimumReinforcements( i ) ) + { + iApplicablePatrolIds[iApplicablePatrols] = i; + iApplicablePatrols++; + iApplicableRequestPoints += gPatrolGroup[ i ].bWeight; + } } } - } - if( !iApplicableRequestPoints ) - { - return; - } - - //now randomly choose who gets the reinforcements. - // giRequestPoints is the combined sum of all the individual weights of all garrisons and patrols requesting reinforcements - //iRandom = Random( giRequestPoints ); - iRandom = Random( iApplicableRequestPoints ); + if( iApplicableRequestPoints ) + { + //now randomly choose who gets the reinforcements. + // giRequestPoints is the combined sum of all the individual weights of all garrisons and patrols requesting reinforcements + //iRandom = Random( giRequestPoints ); + iRandom = Random( iApplicableRequestPoints ); - iOrigRequestPoints = giRequestPoints; // debug only! + iOrigRequestPoints = giRequestPoints; // debug only! - //go through garrisons first - for( i = 0; i < iApplicableGarrisons; i++ ) - { - iSumOfAllWeights += iWeight; // debug only! - iWeight = gGarrisonGroup[ iApplicableGarrisonIds[i] ].bWeight; - if( iRandom < iWeight ) - { - //This is the group that gets the reinforcements! - if ( SendReinforcementsForGarrison( iApplicableGarrisonIds[i] , usDefencePoints, NULL ) ) - return; - } - iRandom -= iWeight; - } + //go through garrisons first + for( i = 0; i < iApplicableGarrisons; i++ ) + { + iSumOfAllWeights += iWeight; // debug only! + iWeight = gGarrisonGroup[ iApplicableGarrisonIds[i] ].bWeight; + if( iRandom < iWeight ) + { + //This is the group that gets the reinforcements! + if ( SendReinforcementsForGarrison( iApplicableGarrisonIds[i] , usDefencePoints, NULL ) ) + return; + } + iRandom -= iWeight; + } - //go through the patrol groups - for( i = 0; i < iApplicablePatrols; i++ ) - { - iSumOfAllWeights += iWeight; // debug only! - iWeight = gPatrolGroup[ iApplicablePatrolIds[i] ].bWeight; - if( iRandom < iWeight ) - { - //This is the group that gets the reinforcements! - if ( SendReinforcementsForPatrol( iApplicablePatrolIds[i], NULL ) ) - return; + //go through the patrol groups + for( i = 0; i < iApplicablePatrols; i++ ) + { + iSumOfAllWeights += iWeight; // debug only! + iWeight = gPatrolGroup[ iApplicablePatrolIds[i] ].bWeight; + if( iRandom < iWeight ) + { + //This is the group that gets the reinforcements! + if ( SendReinforcementsForPatrol( iApplicablePatrolIds[i], NULL ) ) + return; + } + iRandom -= iWeight; + } } - iRandom -= iWeight; } ValidateWeights( 27 ); @@ -5125,7 +5132,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto if ( ubNumSoldiers ) { InitializeGroup(GROUP_TYPE_ATTACK, ubNumSoldiers, grouptroops[0], groupelites[0], grouprobots[0], groupjeeps[0], grouptanks[0], Random(10) < difficultyMod); - totalusedsoldiers += grouptroops[0] + groupelites[0] + grouprobots[0], grouptanks[0] + groupjeeps[0]; + totalusedsoldiers += grouptroops[0] + groupelites[0] + grouprobots[0] + grouptanks[0] + groupjeeps[0]; } pGroup = CreateNewEnemyGroupDepartingFromSector( SECTOR( gModSettings.ubSAISpawnSectorX, gModSettings.ubSAISpawnSectorY ), 0, grouptroops[0], groupelites[0], grouprobots[0], grouptanks[0], groupjeeps[0] ); @@ -5386,6 +5393,14 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto gubNumAwareBattles = zDiffSetting[gGameOptions.ubDifficultyLevel].iNumAwareBattles; break; + case NPC_ACTION_DEPLOY_TRANSPORT_GROUP: + DeployTransportGroup(); + break; + + case NPC_ACTION_RETURN_TRANSPORT_GROUP: + ReturnTransportGroup(option1); + break; + default: ScreenMsg( FONT_RED, MSG_DEBUG, L"QueenAI failed to handle action code %d.", usActionCode ); break; @@ -6403,8 +6418,14 @@ void UpgradeAdminsToTroops() // if there are any admins currently in this group if ( pGroup->pEnemyGroup->ubNumAdmins > 0 ) { + // skip transport groups + if (pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + pGroup = pGroup->next; + continue; + } // if it's a patrol group - if ( pGroup->pEnemyGroup->ubIntention == PATROL ) + else if ( pGroup->pEnemyGroup->ubIntention == PATROL ) { sPatrolIndex = FindPatrolGroupIndexForGroupID( pGroup->ubGroupID ); Assert( sPatrolIndex != -1 ); @@ -6508,9 +6529,17 @@ INT16 FindGarrisonIndexForGroupIDPending( UINT8 ubGroupID ) void TransferGroupToPool( GROUP **pGroup ) { - //Madd: unlimited reinforcements? - if ( !gfUnlimitedTroops ) - giReinforcementPool += (*pGroup)->ubGroupSize; + if ((*pGroup)->usGroupTeam == ENEMY_TEAM) + { + //Madd: unlimited reinforcements? + if ( !gfUnlimitedTroops ) + giReinforcementPool += (*pGroup)->ubGroupSize; + + AddStrategicAIResources(ASD_ROBOT, (*pGroup)->pEnemyGroup->ubNumRobots); + AddStrategicAIResources(ASD_JEEP, (*pGroup)->pEnemyGroup->ubNumJeeps); + AddStrategicAIResources(ASD_TANK, (*pGroup)->pEnemyGroup->ubNumTanks); + } + RemovePGroup( *pGroup ); *pGroup = NULL; diff --git a/Strategic/Strategic AI.h b/Strategic/Strategic AI.h index 77d9c3a62..5c7986af3 100644 --- a/Strategic/Strategic AI.h +++ b/Strategic/Strategic AI.h @@ -38,6 +38,7 @@ BOOLEAN StrategicAILookForAdjacentGroups( GROUP *pGroup ); void RemoveGroupFromStrategicAILists( UINT8 ubGroupID ); void RecalculateSectorWeight( UINT8 ubSectorID ); void RecalculateGroupWeight( GROUP *pGroup ); +void SendGroupToPool( GROUP **pGroup ); BOOLEAN OkayForEnemyToMoveThroughSector( UINT8 ubSectorID ); BOOLEAN EnemyPermittedToAttackSector( GROUP **pGroup, UINT8 ubSectorID ); @@ -78,7 +79,8 @@ BOOLEAN PermittedToFillPatrolGroup( INT32 iPatrolID ); enum GROUP_TYPE { GROUP_TYPE_ATTACK, - GROUP_TYPE_PATROL + GROUP_TYPE_PATROL, + GROUP_TYPE_TRANSPORT }; void ASDInitializePatrolGroup(GROUP *pGroup); diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index 5b884b569..881783a18 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -50,6 +50,7 @@ #include "Creature Spreading.h" // added by Flugente #include "MilitiaIndividual.h" // added by Flugente #include "Rebel Command.h" + #include "Strategic Transport Groups.h" #include "MilitiaSquads.h" #include "Vehicles.h" @@ -3635,7 +3636,10 @@ INT32 GetSectorMvtTimeForGroup( UINT8 ubSector, UINT8 ubDirection, GROUP *pGroup dEnemyGeneralsSpeedupFactor = max( 0.75f, dEnemyGeneralsSpeedupFactor - gStrategicStatus.usVIPsLeft * gGameExternalOptions.fEnemyGeneralStrategicMovementSpeedBonus ); } - iBestTraverseTime = dEnemyGeneralsSpeedupFactor * iBestTraverseTime; + // rftr: transport groups move slower than normal + const FLOAT transportSpeedFactor = pGroup->pEnemyGroup->ubIntention == TRANSPORT ? 2.0f : 1.0f; + + iBestTraverseTime = dEnemyGeneralsSpeedupFactor * transportSpeedFactor * iBestTraverseTime; iBestTraverseTime = iBestTraverseTime * (100 + RebelCommand::GetHarriersSpeedPenalty(ubSector)) / 100; } @@ -3816,6 +3820,13 @@ void HandleArrivalOfReinforcements( GROUP *pGroup ) ResetMortarsOnTeamCount(); ResetNumSquadleadersInArmyGroup(); // added by SANDRO AddPossiblePendingEnemiesToBattle(); + + if (pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + // normally, transport groups can't reinforce, but this can be hit normally if a battle is occuring in a sector + // where a transport group is moving into. + UpdateTransportGroupInventory(); + } } else if ( pGroup->usGroupTeam == MILITIA_TEAM ) { diff --git a/Strategic/Strategic Movement.h b/Strategic/Strategic Movement.h index d8bdfacd1..03f0d3ff6 100644 --- a/Strategic/Strategic Movement.h +++ b/Strategic/Strategic Movement.h @@ -15,6 +15,7 @@ enum //enemy intentions, PATROL, //enemy is moving around determining safe areas. REINFORCEMENTS, //enemy group has intentions to fortify position at final destination. ASSAULT, //enemy is ready to fight anything they encounter. + TRANSPORT, //rftr: enemy is carrying out non-combat tasks, but still has an escort NUM_ENEMY_INTENTIONS }; @@ -109,6 +110,9 @@ typedef struct ENEMYGROUP #define GROUPFLAG_KNOWN_DIRECTION 0x00000080 #define GROUPFLAG_KNOWN_NUMBER 0x00000100 +// rftr: strategic transport group direction flag +#define GROUPFLAG_TRANSPORT_ENROUTE 0x00000200 + typedef struct GROUP { BOOLEAN fDebugGroup; //for testing purposes -- handled differently in certain cases. diff --git a/Strategic/Strategic Transport Groups.cpp b/Strategic/Strategic Transport Groups.cpp new file mode 100644 index 000000000..4451b5a67 --- /dev/null +++ b/Strategic/Strategic Transport Groups.cpp @@ -0,0 +1,1025 @@ +//#pragma optimize("",off) +/* +Strategic Transport Groups +by rftr + +Strategic transport groups are a type of enemy strategic group in addition to ATTACK and PATROL groups. The primary purpose +of transport groups is to function as a loot pinata for the player. However, they will generally be behind enemy lines, so +the player will have to seek them out. + +Behaviourally, transport groups will spawn at the AI's HQ and then travel to a friendly town. Once the group reaches its +destination, it will wait for a few hours before returning home, where it despawns. The AI will receive small bonuses +upon the group successfully reaching both its destination and HQ. + +A transport group always flags its members to drop everything they have regardless of the player's DROP_ALL setting. In +addition, transport groups will also be carrying supplies that the player may find useful. + +Transport group compositions will vary based on the player's progress, how many interceptions have been completed recently, +and the difficulty of the game. + +*/ +#include "Strategic Transport Groups.h" + +#include "ASD.h" +#include "Assignments.h" +#include "Campaign.h" +#include "Game Clock.h" +#include "Game Event Hook.h" +#include "GameSettings.h" +#include "Inventory Choosing.h" +#include "Map Screen Interface Map.h" +#include "message.h" +#include "Overhead.h" +#include "Overhead Types.h" +#include "Queen Command.h" +#include "random.h" +#include "Soldier Control.h" +#include "strategic.h" +#include "Strategic AI.h" +#include "strategicmap.h" +#include "Strategic Mines.h" +#include "Strategic Movement.h" +#include "Strategic Town Loyalty.h" + +#define TRANSPORT_GROUP_DEBUG(x, ...) if (gGameExternalOptions.fStrategicTransportGroupsDebug) {ScreenMsg(FONT_RED, MSG_INTERFACE, x, __VA_ARGS__);} + +// how many turncoats are required to monitor a town for transport groups? +#define ELITE_TURNCOAT_MONITOR_REQUIREMENT 1 +#define TROOP_TURNCOAT_MONITOR_REQUIREMENT 3 +#define ADMIN_TURNCOAT_MONITOR_REQUIREMENT 5 + +extern ARMY_GUN_CHOICE_TYPE gExtendedArmyGunChoices[SOLDIER_GUN_CHOICE_SELECTIONS][ARMY_GUN_LEVELS]; +extern ARMY_GUN_CHOICE_TYPE gArmyItemChoices[SOLDIER_GUN_CHOICE_SELECTIONS][MAX_ITEM_TYPES]; +extern BOOLEAN gfTownUsesLoyalty[MAX_TOWNS]; + +std::map> transportGroupIdToSoldierMap; +std::map transportGroupSectorInfo; + +void PopulateTransportGroup(UINT8& admins, UINT8& troops, UINT8& elites, UINT8& jeeps, UINT8& tanks, UINT8& robots, UINT8 progress, int difficulty, BOOLEAN trySpecialCase); + +BOOLEAN DeployTransportGroup() +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return FALSE; + + if (giReinforcementPool <= 0) + return FALSE; + + const UINT8 difficulty = gGameOptions.ubDifficultyLevel; + + // is there a mine here? + std::vector mineSectorIds; + for (INT8 i = 0; i < NUM_TOWNS; ++i) + { + // skip towns that have no loyalty + if (!gfTownUsesLoyalty[i]) continue; + + // filter by TOWN ownership - skip contested towns on expert/insane + if (IsTownUnderCompleteControlByPlayer(i)) continue; + if ((difficulty == DIF_LEVEL_HARD || difficulty == DIF_LEVEL_INSANE) && IsTownUnderCompleteControlByEnemy(i) == FALSE) continue; + + // skip towns with a shut down mine + const INT8 mineIndex = GetMineIndexForTown(i); + if (mineIndex == -1) continue; + if (IsMineShutDown(mineIndex) == TRUE) continue; + + // filter by MINE ownership - for novice/experienced, as hard/insane would have ignored this town above + const INT16 mineSector = (GetMineSectorForTown(i)); + if (StrategicMap[mineSector].fEnemyControlled == FALSE) continue; + + mineSectorIds.push_back(STRATEGIC_INDEX_TO_SECTOR_INFO(mineSector)); + } + + for (int a = 0; a < mineSectorIds.size(); ++a) + { + TRANSPORT_GROUP_DEBUG(L"DeployTransportGroup valid town destination: %d (%d/%d)", mineSectorIds[a], SECTORX(mineSectorIds[a]), SECTORY(mineSectorIds[a])); + } + + // no valid destinations + if (mineSectorIds.size() == 0) return FALSE; + + INT8 transportGroupCount = 0; + GROUP* pGroup = gpGroupList; + std::vector> groupIds; + while (pGroup) + { + if (pGroup->usGroupTeam == ENEMY_TEAM && pGroup->pEnemyGroup->ubIntention == TRANSPORT) + { + groupIds.emplace_back(pGroup->ubGroupID, pGroup->ubSectorX, pGroup->ubSectorY); + transportGroupCount++; + } + pGroup = pGroup->next; + } + + for (int a = 0; a < groupIds.size(); ++a) + { + TRANSPORT_GROUP_DEBUG(L"DeployTransportGroup found existing transport groupid: %d at %d/%d", std::get<0>(groupIds[a]), std::get<1>(groupIds[a]), std::get<2>(groupIds[a])); + } + + // track recent transport group interceptions + const INT8 recentLossCount = min(5, GetAllStrategicEventsOfType(EVENT_TRANSPORT_GROUP_DEFEATED).size()); + + // if there are too many active transport groups, don't deploy any more + // maximum number of active groups is the number of valid destinations at queen decision time + const INT8 maxGroups = gGameExternalOptions.iMaxSimultaneousTransportGroups - recentLossCount; + if (transportGroupCount >= min(max(maxGroups, 0), mineSectorIds.size())) return FALSE; + + const UINT8 ubSectorID = (UINT8)mineSectorIds[Random(mineSectorIds.size())]; + + TRANSPORT_GROUP_DEBUG(L"DeployTransportGroup sending group to sectorId: %d (%d/%d)", ubSectorID, SECTORX(ubSectorID), SECTORY(ubSectorID)); + + UINT8 admins, troops, elites, robots, jeeps, tanks; + const UINT8 progress = min(125, HighestPlayerProgressPercentage() + recentLossCount * 5); + PopulateTransportGroup(admins, troops, elites, jeeps, tanks, robots, progress, difficulty, mineSectorIds.size() == 1); + + // varying transport group quality/compositions + pGroup = CreateNewEnemyGroupDepartingFromSector( SECTOR( gModSettings.ubSAISpawnSectorX, gModSettings.ubSAISpawnSectorY ), admins, troops, elites, robots, tanks, jeeps ); + + //Madd: unlimited reinforcements? + if ( !gfUnlimitedTroops ) + { + giReinforcementPool -= (admins + troops + elites + robots + jeeps + tanks); + + giReinforcementPool = max( giReinforcementPool, 0 ); + } + + MoveSAIGroupToSector( &pGroup, ubSectorID, EVASIVE, TRANSPORT ); + + pGroup->uiFlags |= GROUPFLAG_TRANSPORT_ENROUTE; + + return TRUE; +} + +BOOLEAN ForceDeployTransportGroup(UINT8 sectorId) +{ + UINT8 admins, troops, elites, robots, jeeps, tanks; + const INT8 recentLossCount = min(5, GetAllStrategicEventsOfType(EVENT_TRANSPORT_GROUP_DEFEATED).size()); + const UINT8 progress = min(125, HighestPlayerProgressPercentage() + recentLossCount * 5); + const UINT8 difficulty = gGameOptions.ubDifficultyLevel; + PopulateTransportGroup(admins, troops, elites, jeeps, tanks, robots, progress, difficulty, FALSE); + return TRUE; +} + +BOOLEAN ReturnTransportGroup(INT32 groupId) +{ + GROUP* pGroup = gpGroupList; + while (pGroup) + { + if (pGroup->ubGroupID == groupId) + { + MoveSAIGroupToSector( &pGroup, SECTOR( gModSettings.ubSAISpawnSectorX, gModSettings.ubSAISpawnSectorY ), EVASIVE, TRANSPORT ); + if (pGroup) + pGroup->uiFlags &= ~GROUPFLAG_TRANSPORT_ENROUTE; + break; + } + pGroup = pGroup->next; + } + + if (pGroup == nullptr) + { + TRANSPORT_GROUP_DEBUG(L"RETURN_TRANSPORT_GROUP failed to find groupid %d", groupId); + return FALSE; + } + + return TRUE; +} + +void FillMapColoursForTransportGroups(INT32(&colorMap)[MAXIMUM_VALID_Y_COORDINATE][MAXIMUM_VALID_X_COORDINATE]) +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return; + + const auto debugColor = MAP_SHADE_LT_BLUE; + const auto targetColor = MAP_SHADE_LT_YELLOW; + const INT8 DETECTION_RANGE_SCOUT = 1; + const INT8 DETECTION_RANGE_RADIO = 3; + const INT8 DETECTION_RANGE_COVERT = 0; + GROUP* pGroup = gpGroupList; + transportGroupSectorInfo.clear(); + + enum class MonitoredSectorState { + Unmonitored, + Monitored, + GroupIncoming, + } monitoredSectorState; + + // build map of detection sectors + ranges + std::map, INT8> detectionMap; + std::map monitoredTowns; + for( INT16 i = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; i <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; i++ ) + { + if( MercPtrs[ i ]->bActive && + MercPtrs[ i ]->stats.bLife >= OKLIFE && + (MercPtrs[ i ]->bAssignment < ON_DUTY || MercPtrs[ i ]->bAssignment == GATHERINTEL) && + !MercPtrs[ i ]->flags.fMercAsleep) + { + if (gGameOptions.fNewTraitSystem) + { + if (HAS_SKILL_TRAIT(MercPtrs[i], SCOUTING_NT)) + { + detectionMap[std::pair(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = DETECTION_RANGE_SCOUT; + } + else if (HAS_SKILL_TRAIT(MercPtrs[i], RADIO_OPERATOR_NT) && MercPtrs[i]->CanUseRadio(FALSE)) + { + detectionMap[std::pair(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = DETECTION_RANGE_RADIO; + } + else if (HAS_SKILL_TRAIT(MercPtrs[i], COVERT_NT)) + { + if (MercPtrs[i]->bAssignment == GATHERINTEL) + { + detectionMap[std::pair(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = DETECTION_RANGE_COVERT; + monitoredTowns[GetTownIdForSector(MercPtrs[i]->sSectorX, MercPtrs[i]->sSectorY)] = MonitoredSectorState::Monitored; + } + } + } + } + } + + // turncoats in towns can detect incoming transport groups + for (int x = MINIMUM_VALID_X_COORDINATE; x <= MAXIMUM_VALID_X_COORDINATE; ++x) + { + for (int y = MINIMUM_VALID_Y_COORDINATE; y <= MAXIMUM_VALID_Y_COORDINATE; ++y) + { + const UINT8 townId = GetTownIdForSector(x, y); + if (townId == BLANK_SECTOR) continue; + + CorrectTurncoatCount(x, y); + const UINT16 adminTurncoats = NumTurncoatsOfClassInSector(x, y, SOLDIER_CLASS_ADMINISTRATOR); + const UINT16 troopTurncoats = NumTurncoatsOfClassInSector(x, y, SOLDIER_CLASS_ARMY); + const UINT16 eliteTurncoats = NumTurncoatsOfClassInSector(x, y, SOLDIER_CLASS_ELITE); + + if (gGameExternalOptions.fStrategicTransportGroupsDebug + || (adminTurncoats >= ADMIN_TURNCOAT_MONITOR_REQUIREMENT) + || (troopTurncoats >= TROOP_TURNCOAT_MONITOR_REQUIREMENT) + || (eliteTurncoats >= ELITE_TURNCOAT_MONITOR_REQUIREMENT)) monitoredTowns[townId] = MonitoredSectorState::Monitored; + } + } + + // colour all groups + while (pGroup) + { + if (pGroup->usGroupTeam == ENEMY_TEAM) + { + const UINT8 intention = pGroup->pEnemyGroup->ubIntention; + if (intention == TRANSPORT ) + { + // check if current location is known + const INT16 gx = pGroup->ubSectorX; + const INT16 gy = pGroup->ubSectorY; + for (const auto key : detectionMap) + { + const std::pair sector = key.first; + const INT8 range = key.second; + + const INT8 dist = abs((gx - sector.first)) + abs((gy - sector.second)); + if (dist <= range) + { + colorMap[pGroup->ubSectorY-1][pGroup->ubSectorX-1] = targetColor; + transportGroupSectorInfo[SECTOR(pGroup->ubSectorX, pGroup->ubSectorY)] = TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedGroup; + } + } + + // turncoats reveal their group's location + if (pGroup->pEnemyGroup->ubNumAdmins_Turncoat >= ADMIN_TURNCOAT_MONITOR_REQUIREMENT + || pGroup->pEnemyGroup->ubNumTroops_Turncoat >= TROOP_TURNCOAT_MONITOR_REQUIREMENT + || pGroup->pEnemyGroup->ubNumElites_Turncoat >= ELITE_TURNCOAT_MONITOR_REQUIREMENT) + { + colorMap[pGroup->ubSectorY-1][pGroup->ubSectorX-1] = targetColor; + transportGroupSectorInfo[SECTOR(pGroup->ubSectorX, pGroup->ubSectorY)] = TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedGroup; + } + + // check if target location is monitored + WAYPOINT* wp = pGroup->pWaypoints; + + while (wp) + { + if (wp->next == nullptr) + break; + + wp = wp->next; + } + + if (wp == nullptr) + { + // ignore this group - it doesn't have a waypoint (?) + continue; + } + + const UINT8 townId = GetTownIdForSector(wp->x, wp->y); + if (monitoredTowns.find(townId) != monitoredTowns.end() && monitoredTowns[townId] == MonitoredSectorState::Monitored) + { + monitoredTowns[townId] = MonitoredSectorState::GroupIncoming; + } + + // debug: colour all group locations + if (gGameExternalOptions.fStrategicTransportGroupsDebug) + { + colorMap[pGroup->ubSectorY-1][pGroup->ubSectorX-1] = debugColor; + } + } + } + + pGroup = pGroup->next; + } + + // color all monitored towns if there's a transport group en route + for (int x = MINIMUM_VALID_X_COORDINATE; x <= MAXIMUM_VALID_X_COORDINATE; ++x) + { + for (int y = MINIMUM_VALID_Y_COORDINATE; y <= MAXIMUM_VALID_Y_COORDINATE; ++y) + { + const UINT8 townId = GetTownIdForSector(x, y); + if (monitoredTowns.find(townId) != monitoredTowns.end() && monitoredTowns[townId] == MonitoredSectorState::GroupIncoming) + { + colorMap[y-1][x-1] = targetColor; + transportGroupSectorInfo[SECTOR(x, y)] = TransportGroupSectorInfo::TransportGroupSectorInfo_LocatedDestination; + } + } + } + +} + +void ProcessTransportGroupReachedDestination(GROUP* pGroup) +{ + const UINT8 difficulty = gGameOptions.ubDifficultyLevel; + + // just arrived, let's go home + if (pGroup->ubSectorX != gModSettings.ubSAISpawnSectorX && pGroup->ubSectorY != gModSettings.ubSAISpawnSectorY) + { + pGroup->ubSectorIDOfLastReassignment = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); + + // global loyalty loss + INT32 loyaltyLoss = 0; + switch (difficulty) + { + case DIF_LEVEL_EASY: loyaltyLoss = 0; break; + case DIF_LEVEL_MEDIUM: loyaltyLoss = 0; break; + case DIF_LEVEL_HARD: loyaltyLoss = -100; break; + case DIF_LEVEL_INSANE: loyaltyLoss = -250; break; + } + + AffectAllTownsLoyaltyByDistanceFrom(loyaltyLoss, pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ); + + // queue up return home order + AddStrategicEvent(EVENT_RETURN_TRANSPORT_GROUP, GetWorldTotalMin() + 60 * 6, pGroup->ubGroupID); + } + else + { + // asd income injection and bonus update + if (gGameExternalOptions.fASDActive) + { + INT32 moneyAmt = 0; + INT32 fuelAmt = 0; + + switch (difficulty) + { + case DIF_LEVEL_EASY: + moneyAmt = 0; + fuelAmt = 0; + break; + + case DIF_LEVEL_MEDIUM: + moneyAmt = gGameExternalOptions.gASDResource_Cost[ASD_JEEP] * 0.25f; + fuelAmt = gGameExternalOptions.gASDResource_Fuel_Jeep * 0.25f; + break; + + case DIF_LEVEL_HARD: + moneyAmt = gGameExternalOptions.gASDResource_Cost[ASD_JEEP] * 0.5f; + fuelAmt = gGameExternalOptions.gASDResource_Fuel_Jeep * 0.5f; + break; + + case DIF_LEVEL_INSANE: + moneyAmt = gGameExternalOptions.gASDResource_Cost[ASD_TANK]; + fuelAmt = gGameExternalOptions.gASDResource_Fuel_Tank; + break; + } + + AddStrategicAIResources(ASD_MONEY, moneyAmt); + AddStrategicAIResources(ASD_FUEL, fuelAmt); + UpdateASD(); + } + + // reinforcement pool increase + if (!gfUnlimitedTroops) + { + INT32 poolAmt = 0; + switch (difficulty) + { + case DIF_LEVEL_EASY: poolAmt = 0; break; + case DIF_LEVEL_MEDIUM: poolAmt = 10; break; + case DIF_LEVEL_HARD: poolAmt = 15; break; + case DIF_LEVEL_INSANE: poolAmt = 40; break; + } + + giReinforcementPool += poolAmt; + } + + // successfully returned home. give the strategic ai some rewards! + SendGroupToPool(&pGroup); + + if (difficulty != DIF_LEVEL_EASY) + { + // immediately do a queen evaluation + DeleteAllStrategicEventsOfType(EVENT_EVALUATE_QUEEN_SITUATION); + EvaluateQueenSituation(); + } + } +} + +void UpdateTransportGroupInventory() +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return; + + const int firstSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; + const int lastSlot = gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; + const UINT8 progress = CurrentPlayerProgressPercentage(); + + enum ItemTypes + { + GAS_CANS, + MEDICAL_FIRSTAIDKITS, + MEDICAL_MEDKITS, + MEDICAL_OTHER, + TOOL_KITS, + BACKPACKS, + RADIOS, + ATTACHMENTS, + CAMO_KITS, + MISC, + GRENADE_THROWN, + GUNS, + AMMO_BOXES, + GRENADELAUNCHERS, + ROCKETLAUNCHERS, + + + TRANSPORT_LOOT_START = GAS_CANS, + TRANSPORT_LOOT_END = ROCKETLAUNCHERS, + }; + + std::map> itemMap; + + // item cache build + { + // let's be nice to the player and only drop ammo for guns their mercs have in inventory + std::set playerCalibres; + for (INT16 i = gTacticalStatus.Team[OUR_TEAM].bFirstID; i <= gTacticalStatus.Team[OUR_TEAM].bLastID; i++) + { + if (MercPtrs[i]->bActive && !(MercPtrs[i]->flags.uiStatusFlags & SOLDIER_VEHICLE)) + { + for (int j = 0 ; j < MercPtrs[i]->inv.size(); ++j) + { + OBJECTTYPE& obj = MercPtrs[i]->inv[j]; + if (obj.exists() + && Item[obj.usItem].usItemClass == IC_GUN) + { + playerCalibres.insert(Weapon[Item[obj.usItem].ubClassIndex].ubCalibre); + } + } + } + } + + for (UINT16 i = 0; i < gMAXITEMS_READ; ++i) + { + if (!ItemIsLegal(i, TRUE)) continue; + if ((Item[i].usItemClass & IC_AMMO) == 0 && (Item[i].iTransportGroupMaxProgress == 0 || Item[i].iTransportGroupMinProgress > progress || progress > Item[i].iTransportGroupMaxProgress)) continue; + + if (Item[i].medical) + { + if (Item[i].firstaidkit) itemMap[MEDICAL_FIRSTAIDKITS].push_back(i); + else if (Item[i].medicalkit) itemMap[MEDICAL_MEDKITS].push_back(i); + else itemMap[MEDICAL_OTHER].push_back(i); + } + else if (Item[i].gascan) itemMap[GAS_CANS].push_back(i); + else if (Item[i].toolkit) itemMap[TOOL_KITS].push_back(i); + else if (HasItemFlag(i, RADIO_SET)) itemMap[RADIOS].push_back(i); + else if (Item[i].usItemClass & IC_GRENADE) + { + if (Item[i].glgrenade == 0 + && Item[i].attachmentclass != AC_GRENADE // not for a grenade launcher + && Item[i].attachmentclass != AC_ROCKET) // not for a rocket launcher + itemMap[GRENADE_THROWN].push_back(i); + } + else if (Item[i].usItemClass & IC_LBEGEAR) + { + if (LoadBearingEquipment[Item[i].ubClassIndex].lbeClass == BACKPACK && !HasItemFlag(i, RADIO_SET)) // make sure radios don't get added here + { + itemMap[BACKPACKS].push_back(i); + } + } + else if (Item[i].camouflagekit) itemMap[CAMO_KITS].push_back(i); + else if (Item[i].usItemClass & IC_MISC) + { + switch (Item[i].attachmentclass) + { + case AC_BIPOD: + case AC_MUZZLE: + case AC_LASER: + case AC_SIGHT: + case AC_SCOPE: + case AC_FOREGRIP: + itemMap[ATTACHMENTS].push_back(i); + break; + default: + itemMap[MISC].push_back(i); + break; + } + } + else if (Item[i].usItemClass & IC_AMMO) + { + if (Magazine[Item[i].ubClassIndex].ubMagType == AMMO_BOX && playerCalibres.find(Magazine[Item[i].ubClassIndex].ubCalibre) != playerCalibres.end()) + { + if (Item[i].ubCoolness <= ((progress+5) / 10)+2) + { + itemMap[AMMO_BOXES].push_back(i); + } + } + } + else if (Item[i].usItemClass & IC_GUN) + { + itemMap[GUNS].push_back(i); + } + else if (Item[i].grenadelauncher) + { + itemMap[GRENADELAUNCHERS].push_back(i); + } + else if (Item[i].rocketlauncher) + { + itemMap[ROCKETLAUNCHERS].push_back(i); + } + } + } + + auto addItemToInventory = [](SOLDIERTYPE* pSoldier, UINT16 itemId, UINT8 amount) + { + OBJECTTYPE itemToAdd; + CreateItems(itemId, 100, amount, &itemToAdd); + + itemToAdd.fFlags &= ~OBJECT_UNDROPPABLE; + + if (FitsInSmallPocket(&itemToAdd)) + { + for(INT8 i = SMALLPOCKSTART; i < SMALLPOCKFINAL; i++ ) + { + if( pSoldier->inv[ i ].exists() == false && !(pSoldier->inv[ i ].fFlags & OBJECT_NO_OVERWRITE) ) + { + pSoldier->inv[ i ] = itemToAdd; + break; + } + } + } + else + { + for(INT8 i = BIGPOCKSTART; i < BIGPOCKFINAL; i++ ) + { //no space free in small pockets, so put it into a large pocket. + if( pSoldier->inv[ i ].exists() == false && !(pSoldier->inv[ i ].fFlags & OBJECT_NO_OVERWRITE) ) + { + pSoldier->inv[ i ] = itemToAdd; + break; + } + } + } + }; + + // cache the initial jeep count of every group we find + std::map cachedGroupJeepCount; + for (int slot = firstSlot; (slot <= lastSlot); ++slot) + { + SOLDIERTYPE* pSoldier = &Menptr[slot]; + + const std::map>::iterator groupIter = transportGroupIdToSoldierMap.find(pSoldier->ubGroupID); + if (groupIter != transportGroupIdToSoldierMap.end()) + { + // let's find out if this group is coming home or still outgoing to its target destination + GROUP* pGroup = gpGroupList; + BOOLEAN outgoing = FALSE; + while (pGroup) + { + if (pGroup->ubGroupID == groupIter->first) + { + outgoing = pGroup->uiFlags & GROUPFLAG_TRANSPORT_ENROUTE; + break; + } + pGroup = pGroup->next; + } + + // found a matching transport groupid + std::map::iterator soldierClassIter = groupIter->second.find(SOLDIER_CLASS_JEEP); + if (cachedGroupJeepCount.find(groupIter->first) == cachedGroupJeepCount.end()) + { + cachedGroupJeepCount[groupIter->first] = soldierClassIter == groupIter->second.end() ? 0 : groupIter->second[SOLDIER_CLASS_JEEP]; + } + + if (soldierClassIter != groupIter->second.end() && cachedGroupJeepCount.find(groupIter->first) != cachedGroupJeepCount.end() && cachedGroupJeepCount[groupIter->first] > 0) + { + TRANSPORT_GROUP_DEBUG(L"Found groupid[%d] with admin[%d] troop[%d] elite[%d] jeep[%d]", groupIter->first, groupIter->second[SOLDIER_CLASS_ADMINISTRATOR], groupIter->second[SOLDIER_CLASS_ARMY], groupIter->second[SOLDIER_CLASS_ELITE], groupIter->second[SOLDIER_CLASS_JEEP]); + // this group has a jeep in it! + // only jeeps carry things + // but give a little extra, since the jeep exploding can outright destroy things + if (pSoldier->ubSoldierClass == SOLDIER_CLASS_JEEP) + { + //if (outgoing) + { + // en route to target destination - carrying ammo, supplies, etc + for (int i = TRANSPORT_LOOT_START; i <= TRANSPORT_LOOT_END; ++i) + { + const ItemTypes itemType = static_cast(i); + if (itemMap[itemType].size() > 0) + { + const UINT16 id = itemMap[itemType][Random(itemMap[itemType].size())]; + switch (itemType) + { + case BACKPACKS: + case RADIOS: + case MISC: + case AMMO_BOXES: + // intentionally do nothing + break; + + case GAS_CANS: + addItemToInventory(pSoldier, id, 1); + break; + + case MEDICAL_MEDKITS: + case MEDICAL_OTHER: + case TOOL_KITS: + addItemToInventory(pSoldier, id, 2); + break; + + case MEDICAL_FIRSTAIDKITS: + addItemToInventory(pSoldier, id, 10); + break; + + case GRENADE_THROWN: + addItemToInventory(pSoldier, id, 12); + break; + + case GUNS: + { + for (int loop = 0; loop < 3; ++loop) + { + const UINT16 gunId = itemMap[itemType][Random(itemMap[itemType].size())]; + addItemToInventory(pSoldier, gunId, 1); + + UINT16 ammoId = RandomMagazine(gunId, 0, 100, SOLDIER_CLASS_ELITE); + if (ammoId == 0) continue; // no ammo matches, skip + + BOOLEAN convertedToBox = FALSE; + for (INT32 itemId = 0; itemId < (INT32)gMAXITEMS_READ; ++itemId) + { + if( ItemIsLegal(itemId) + && Item[itemId].usItemClass == IC_AMMO + && Magazine[Item[itemId].ubClassIndex].ubMagType == AMMO_BOX + && Magazine[Item[itemId].ubClassIndex].ubCalibre == Magazine[Item[ammoId].ubClassIndex].ubCalibre + && Magazine[Item[itemId].ubClassIndex].ubAmmoType == Magazine[Item[ammoId].ubClassIndex].ubAmmoType) + { + // replace mag with box + convertedToBox = TRUE; + ammoId = itemId; + break; + } + } + addItemToInventory(pSoldier, ammoId, convertedToBox ? 2 : 10); + } + } + break; + + case GRENADELAUNCHERS: + { + addItemToInventory(pSoldier, id, 1); + + const UINT16 launchableId = PickARandomLaunchable(id); + if (launchableId == 0) continue; // no launchable matches, skip + + addItemToInventory(pSoldier, launchableId, 8); + } + break; + + case ROCKETLAUNCHERS: + { + addItemToInventory(pSoldier, id, Item[id].singleshotrocketlauncher ? 3 : 1); + + const UINT16 launchableId = PickARandomLaunchable(id); + if (launchableId == 0) continue; // no launchable matches, skip + + addItemToInventory(pSoldier, launchableId, 3); + } + break; + + case ATTACHMENTS: + for (int loop = 0; loop < 5; ++loop) + { + const UINT16 attachmentId = itemMap[itemType][Random(itemMap[itemType].size())]; + addItemToInventory(pSoldier, attachmentId, 2); + } + break; + + case CAMO_KITS: + addItemToInventory(pSoldier, id, 6); + break; + + default: + TRANSPORT_GROUP_DEBUG(L"Warning: ignoring unhandled transport group loot type: %d", itemType); + // nothing! + break; + } + } + } + } + //else // returning home + { + // I can't really think of a good different loot set for returning transport groups, so we'll have the same loot + // regardless of whether the group is outgoing or incoming. I'll keep the in/out flag in case that changes + } + + transportGroupIdToSoldierMap[pSoldier->ubGroupID][SOLDIER_CLASS_JEEP]--; + } + else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR + || pSoldier->ubSoldierClass == SOLDIER_CLASS_ARMY + || pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE) + { + // jeep is carrying most things, so soldiers just have ammo + if (itemMap[AMMO_BOXES].size() > 0) + { + const UINT16 ammoId = itemMap[AMMO_BOXES][Random(itemMap[AMMO_BOXES].size())]; + addItemToInventory(pSoldier, ammoId, 1); + } + + if (itemMap[BACKPACKS].size() > 0 && UsingNewInventorySystem()) + { + OBJECTTYPE obj; + CreateItem(itemMap[BACKPACKS][Random(itemMap[BACKPACKS].size())], 100, &obj); + obj.fFlags |= OBJECT_UNDROPPABLE; + pSoldier->inv[BPACKPOCKPOS] = obj; + } + + // force inventory to be dropped! + for (int i = 0; i < pSoldier->inv.size(); ++i) + { + OBJECTTYPE* item = &pSoldier->inv[i]; + if (item->exists() && Item[item->usItem].defaultundroppable == FALSE) + { + item->fFlags &= ~OBJECT_UNDROPPABLE; + } + } + transportGroupIdToSoldierMap[pSoldier->ubGroupID][pSoldier->ubSoldierClass]--; + } + } + else + { + TRANSPORT_GROUP_DEBUG(L"Found jeepless groupid[%d] with admin[%d] troop[%d] elite[%d] jeep[%d]", groupIter->first, groupIter->second[SOLDIER_CLASS_ADMINISTRATOR], groupIter->second[SOLDIER_CLASS_ARMY], groupIter->second[SOLDIER_CLASS_ELITE], groupIter->second[SOLDIER_CLASS_JEEP]); + // no jeep in group, add things normally + soldierClassIter = groupIter->second.find(pSoldier->ubSoldierClass); + if (soldierClassIter != groupIter->second.end()) + { + // found a matching soldierclass + if (soldierClassIter->second > 0) + { + //if (outgoing) + { + for (int i = TRANSPORT_LOOT_START; i <= TRANSPORT_LOOT_END; ++i) + { + const ItemTypes itemType = static_cast(i); + if (itemMap[itemType].size() > 0) + { + const UINT16 id = itemMap[itemType][Random(itemMap[itemType].size())]; + switch (itemType) + { + case GAS_CANS: + case MEDICAL_MEDKITS: + case MEDICAL_OTHER: + case TOOL_KITS: + case GUNS: + case GRENADELAUNCHERS: + case ROCKETLAUNCHERS: + case GRENADE_THROWN: + case MISC: + // skip for foot soldiers! + break; + + case BACKPACKS: + { + if (UsingNewInventorySystem()) + { + OBJECTTYPE obj; + CreateItem(id, 100, &obj); + obj.fFlags |= OBJECT_UNDROPPABLE; + pSoldier->inv[BPACKPOCKPOS] = obj; + } + } + break; + + case MEDICAL_FIRSTAIDKITS: + addItemToInventory(pSoldier, id, 1); + break; + + case AMMO_BOXES: + addItemToInventory(pSoldier, id, 1); + break; + + case CAMO_KITS: + addItemToInventory(pSoldier, id, 1); + break; + + case ATTACHMENTS: + // low chance of attachments + if (Random(100) < 25) + addItemToInventory(pSoldier, id, 1); + break; + + default: + TRANSPORT_GROUP_DEBUG(L"Warning: ignoring unhandled transport group loot type: %d", itemType); + // nothing! + break; + } + } + } + } + //else // returning home + { + // I can't really think of a good different loot set for returning transport groups, so we'll have the same loot + // regardless of whether the group is outgoing or incoming. I'll keep the in/out flag in case that changes + } + + // force inventory to be dropped! + for (int i = 0; i < pSoldier->inv.size(); ++i) + { + OBJECTTYPE* item = &pSoldier->inv[i]; + if (item->exists() && Item[item->usItem].defaultundroppable == FALSE) + { + item->fFlags &= ~OBJECT_UNDROPPABLE; + } + } + transportGroupIdToSoldierMap[pSoldier->ubGroupID][pSoldier->ubSoldierClass]--; + } + } + } + } + } +} + +void AddToTransportGroupMap(UINT8 groupId, int soldierClass, UINT8 amount) +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + { + ClearTransportGroupMap(); + return; + } + + // only update admins/troops/elites/jeeps + + switch (soldierClass) + { + case SOLDIER_CLASS_ADMINISTRATOR: + case SOLDIER_CLASS_ARMY: + case SOLDIER_CLASS_ELITE: + case SOLDIER_CLASS_JEEP: + transportGroupIdToSoldierMap[groupId][soldierClass] += amount; + break; + default: + // do nothing! + break; + } +} + +void ClearTransportGroupMap() +{ + transportGroupIdToSoldierMap.clear(); +} + +void NotifyTransportGroupDefeated() +{ + if (gGameExternalOptions.fStrategicTransportGroupsEnabled == FALSE) + return; + + const UINT32 hoursToRememberDefeat = 24 * 7; // 7 days + + AddStrategicEvent(EVENT_TRANSPORT_GROUP_DEFEATED, GetWorldTotalMin() + 60 * hoursToRememberDefeat, 0); +} + +void PopulateTransportGroup(UINT8& admins, UINT8& troops, UINT8& elites, UINT8& jeeps, UINT8& tanks, UINT8& robots, UINT8 progress, int difficulty, BOOLEAN trySpecialCase) +{ + admins = troops = elites = robots = jeeps = tanks = 0; + + // special case for only one valid destination - expert/insane only + if (trySpecialCase && (difficulty == DIF_LEVEL_HARD || difficulty == DIF_LEVEL_INSANE)) + { + admins = 1; + elites = difficulty == DIF_LEVEL_HARD ? 14 : 19; + + if (elites > 0 && gGameExternalOptions.fASDAssignsJeeps && ASDSoldierUpgradeToJeep()) + { + jeeps++; + elites--; + } + + if (gGameExternalOptions.fASDAssignsTanks) + { + const int numTanks = difficulty == DIF_LEVEL_INSANE ? 2 : 1; + for (int i = 0; i < numTanks; ++i) + { + if (elites > 0 && ASDSoldierUpgradeToTank()) + { + tanks++; + elites--; + } + } + } + + if (gGameExternalOptions.fASDAssignsRobots) + { + const int numRobots = Random(5); + for (int i = 0; i < numRobots; ++i) + { + if (elites > 0 && ASDSoldierUpgradeToRobot()) + { + robots++; + elites--; + } + } + } + } + else // normal case + { + UINT8 difficultyMod = 1; + switch (difficulty) + { + case DIF_LEVEL_EASY: difficultyMod = 1; break; + case DIF_LEVEL_MEDIUM: difficultyMod = 2; break; + case DIF_LEVEL_HARD: difficultyMod = 3; break; + case DIF_LEVEL_INSANE: difficultyMod = 4; break; + default: break; + } + + // default composition + if (progress < 25) + { + admins = 8 - difficultyMod; + troops = difficultyMod; + } + else if (progress < 50) + { + admins = 10 - difficultyMod * 2; + troops = difficultyMod; + elites = difficultyMod; + } + else if (progress < 75) + { + admins = 2; + troops = 10 - difficultyMod * 2; + elites = difficultyMod * 2; + } + else if (progress <= 100) // intentional equality + { + admins = 2; + troops = 13 - difficultyMod * 3; + elites = difficultyMod * 3; + } + else // at least one recent interception at max progress + { + admins = 2; + troops = 18 - difficultyMod * 4; + elites = difficultyMod * 4; + } + + // add some vehicles, if possible + if (progress >= gGameExternalOptions.usJeepMinimumProgress) + { + if (elites > 0 && gGameExternalOptions.fASDAssignsJeeps && ASDSoldierUpgradeToJeep()) + { + jeeps++; + elites--; + } + } + + if (progress >= gGameExternalOptions.usTankMinimumProgress && Random(100) < (20 + difficultyMod * 10)) + { + if (elites > 0 && gGameExternalOptions.fASDAssignsTanks && ASDSoldierUpgradeToTank()) + { + tanks++; + elites--; + } + } + + if (progress >= gGameExternalOptions.usRobotMinimumProgress && Random(100) < (20 + difficultyMod * 10)) + { + const int numRobots = Random(difficultyMod + 1); + for (int i = 0; i < numRobots; ++i) + { + if (elites > 0 && gGameExternalOptions.fASDAssignsRobots && ASDSoldierUpgradeToRobot()) + { + robots++; + elites--; + } + } + } + } +} + +const std::map GetTransportGroupSectorInfo() +{ + return transportGroupSectorInfo; +} + +#undef TRANSPORT_GROUP_DEBUG + diff --git a/Strategic/Strategic Transport Groups.h b/Strategic/Strategic Transport Groups.h new file mode 100644 index 000000000..3fd873c54 --- /dev/null +++ b/Strategic/Strategic Transport Groups.h @@ -0,0 +1,31 @@ +#ifndef _STRATEGIC_TRANSPORT_GROUPS_H +#define _STRATEGIC_TRANSPORT_GROUPS_H + +#include "Campaign Types.h" +#include "Types.h" +#include + +enum TransportGroupSectorInfo +{ + TransportGroupSectorInfo_LocatedGroup = 0, + TransportGroupSectorInfo_LocatedDestination, +}; + +struct GROUP; + +BOOLEAN DeployTransportGroup(); +BOOLEAN ForceDeployTransportGroup(UINT8 sectorId); +BOOLEAN ReturnTransportGroup(INT32 groupId); +void FillMapColoursForTransportGroups(INT32(&colorMap)[MAXIMUM_VALID_Y_COORDINATE][MAXIMUM_VALID_X_COORDINATE]); +void ProcessTransportGroupReachedDestination(GROUP* pGroup); +void UpdateTransportGroupInventory(); + +const std::map GetTransportGroupSectorInfo(); + +void AddToTransportGroupMap(UINT8 groupId, int soldierClass, UINT8 amount); +void ClearTransportGroupMap(); + +void NotifyTransportGroupDefeated(); + +#endif + diff --git a/Tactical/Interface Dialogue.cpp b/Tactical/Interface Dialogue.cpp index 16a8bfdf3..00e4334aa 100644 --- a/Tactical/Interface Dialogue.cpp +++ b/Tactical/Interface Dialogue.cpp @@ -3590,6 +3590,7 @@ void HandleNPCDoAction( UINT8 ubTargetNPC, UINT16 usActionCode, UINT8 ubQuoteNum case NPC_ACTION_SEND_SOLDIERS_TO_BALIME: case NPC_ACTION_GLOBAL_OFFENSIVE_1: case NPC_ACTION_GLOBAL_OFFENSIVE_2: + case NPC_ACTION_DEPLOY_TRANSPORT_GROUP: break; case NPC_ACTION_TRIGGER_QUEEN_BY_SAM_SITES_CONTROLLED: diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index 3e91212c0..67e761d42 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -1220,6 +1220,10 @@ typedef struct BOOLEAN fProvidesRobotLaserBonus; //shadooow: bitflag controlling what system needs to be in play for item to appear UINT8 usLimitedToSystem; + + // rftr: the progress bounds that allow a transport group to drop an item + INT8 iTransportGroupMinProgress; + INT8 iTransportGroupMaxProgress; } INVTYPE; diff --git a/Tactical/Rotting Corpses.cpp b/Tactical/Rotting Corpses.cpp index 93882d899..35c4101ba 100644 --- a/Tactical/Rotting Corpses.cpp +++ b/Tactical/Rotting Corpses.cpp @@ -2469,7 +2469,9 @@ void ReduceAmmoDroppedByNonPlayerSoldiers( SOLDIERTYPE *pSoldier, INT32 iInvSlot OBJECTTYPE *pObj = &( pSoldier->inv[ iInvSlot ] ); // if it's ammo - if ( Item[ pObj->usItem ].usItemClass == IC_AMMO ) + if ( Item[ pObj->usItem ].usItemClass == IC_AMMO + && Magazine[Item[pObj->usItem].ubClassIndex].ubMagType != AMMO_BOX + && Magazine[Item[pObj->usItem].ubClassIndex].ubMagType != AMMO_CRATE) { //don't drop all the clips, just a random # of them between 1 and how many there are pObj->ubNumberOfObjects = ( UINT8 ) ( 1 + Random( pObj->ubNumberOfObjects ) ); diff --git a/Tactical/interface Dialogue.h b/Tactical/interface Dialogue.h index 2ddc0605d..9ca784082 100644 --- a/Tactical/interface Dialogue.h +++ b/Tactical/interface Dialogue.h @@ -387,6 +387,10 @@ enum NPC_ACTION_GLOBAL_OFFENSIVE_1, NPC_ACTION_GLOBAL_OFFENSIVE_2, + + // rftr: transport groups + NPC_ACTION_DEPLOY_TRANSPORT_GROUP, + NPC_ACTION_RETURN_TRANSPORT_GROUP, NPC_ACTION_RECRUIT_PROFILE_TO_EPC = 700, NPC_ACTION_UNRECRUIT_EPC = 701, diff --git a/Utils/Text.h b/Utils/Text.h index 74bd5f6b7..21037737a 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -1912,6 +1912,8 @@ enum STR_PB_ZOMBIE, STR_PB_BANDIT, STR_PB_BANDIT_KILLCIVS_IN_SECTOR, + STR_PB_TRANSPORT_GROUP, + STR_PB_TRANSPORT_GROUP_EN_ROUTE, TEXT_NUM_STRATEGIC_TEXT }; diff --git a/Utils/XML_Items.cpp b/Utils/XML_Items.cpp index 7a576d561..c48b2cb28 100644 --- a/Utils/XML_Items.cpp +++ b/Utils/XML_Items.cpp @@ -307,7 +307,9 @@ itemStartElementHandle(void *userData, const XML_Char *name, const XML_Char **at strcmp(name, "ProvidesRobotNightVision") == 0 || strcmp(name, "ProvidesRobotLaserBonus") == 0 || strcmp(name, "FoodSystemExclusive") == 0 || - strcmp(name, "DiseaseSystemExclusive") == 0 + strcmp(name, "DiseaseSystemExclusive") == 0 || + strcmp(name, "TransportGroupMinProgress") == 0 || + strcmp(name, "TransportGroupMaxProgress") == 0 ) { pData->curElement = ELEMENT_PROPERTY; @@ -1505,26 +1507,36 @@ itemEndElementHandle(void *userData, const XML_Char *name) } else if (strcmp(name, "ProvidesRobotNightVision") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; pData->curItem.fProvidesRobotNightVision = (BOOLEAN)atol(pData->szCharData); } else if (strcmp(name, "ProvidesRobotLaserBonus") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; pData->curItem.fProvidesRobotLaserBonus = (BOOLEAN)atol(pData->szCharData); } else if (strcmp(name, "FoodSystemExclusive") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; if (atol(pData->szCharData)) pData->curItem.usLimitedToSystem|= FOOD_SYSTEM_FLAG; } else if (strcmp(name, "DiseaseSystemExclusive") == 0) { - pData->curElement == ELEMENT; + pData->curElement = ELEMENT; if (atol(pData->szCharData)) pData->curItem.usLimitedToSystem|= DISEASE_SYSTEM_FLAG; } + else if (strcmp(name, "TransportGroupMinProgress") == 0) + { + pData->curElement = ELEMENT; + pData->curItem.iTransportGroupMinProgress = (INT8)atoi(pData->szCharData); + } + else if (strcmp(name, "TransportGroupMaxProgress") == 0) + { + pData->curElement = ELEMENT; + pData->curItem.iTransportGroupMaxProgress = (INT8)atoi(pData->szCharData); + } --pData->maxReadDepth; } diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 49f8ee18e..2a943df5e 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"僵尸", //L"Zombie", L"土匪", //L"Bandit", L"土匪杀死了%d名平民,在%s分区。", //注:这里的%d和%s不可以随意放前面或后面,一定要按英文顺序,不然会出错。(%d和%s 在中文中不能反过来。) L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6262,6 +6264,7 @@ STR16 z113FeaturesToggleText[] = L"天气功能:暴风雪", //L"Weather: Snow", L"随机事件功能", //L"Mini Events", L"反抗军司令部功能", //L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6309,6 +6312,7 @@ STR16 z113FeaturesHelpText[] = L"|天|气|功|能|:|暴|风|雪\n \n覆盖 [Tactical Weather Settings] ALLOW_SNOW\n \n暴风雪降低了能见度。\n \n配置选项:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW\n \n", //L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|随|机|事|件|功|能\n \n覆盖 [Mini Events Settings] MINI_EVENTS_ENABLED\n \n可能发生一些随机互动事件。\n \n配置选项:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \n详细信息请查看MiniEvents.lua。\n \n", //L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|反|抗|军|司|令|部|功|能\n \n覆盖 [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \n允许你升级占领的城镇,控制反抗军在战略层面上运作。\n \n详细的内容设定请查看RebelCommand_Settings.ini。\n \n", //L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6356,6 +6360,7 @@ STR16 z113FeaturesPanelText[] = L"启用暴风雪功能。在暴风雪中,更难被看到,武器退化更快,呼吸也更困难。", //L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"在游戏过程中,可能会弹出简短的事件。您可以从两个选项中选择一个,这可能会产生积极或消极的影响。事件可以影响各种各样的事情,但主要是你的佣兵。", //L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"在完成反抗军食物运送任务后,你可以访问他们的(A.R.C)指挥部网站。在这里你可以设定反抗军的政策,也可以为占领区单独设置地方政策。这将带来丰厚的奖励。作为代价,城镇的民忠会上升得更慢,所以你需要更加努力地让当地人信任你。", //L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12098,6 +12103,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"协同行动,悄悄地抵进敌军,但是要小心:这可能会让你部署在劣势区域。当进攻敌军部队时,部署区会更大。", //L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"扰乱ASD", //L"Disrupt ASD", L"破坏Arulco特种部门(ASD)的日常行动。临时阻止ASD部署更多的机械化单位,并且大幅度降低他们的每日收入。", //L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"战略情报", //L"Strategic Intel", L"侦听敌人,发现敌军的攻击目标。当在战略地图上观察队伍时,敌军优先进攻的目标区域会被标红。", //L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"强化本地商店", //L"Improve Local Shops", diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 04da5bd51..00abf9fcb 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6265,6 +6267,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6312,6 +6315,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6359,6 +6363,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12108,6 +12113,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 40ce7ef8a..5f4b9f5e7 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6262,6 +6264,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6309,6 +6312,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6356,6 +6360,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12098,6 +12103,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index 95357d59a..f97f68de8 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -3576,6 +3576,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6270,6 +6272,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6317,6 +6320,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6364,6 +6368,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12090,6 +12095,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index cf3841ba9..0e86f7f69 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -3605,6 +3605,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6133,6 +6135,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6180,6 +6183,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6227,6 +6231,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12012,6 +12017,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index cefb19440..272ff9da7 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -3563,6 +3563,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6251,6 +6253,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6298,6 +6301,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6345,6 +6349,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12099,6 +12104,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index 5d67fe3cc..35548ba15 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -3574,6 +3574,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6266,6 +6268,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6313,6 +6316,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6360,6 +6364,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12112,6 +12117,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops", diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index cdf39d8d2..0345b8301 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -3568,6 +3568,8 @@ STR16 gpStrategicString[] = L"Zombie", L"Bandit", L"Bandits attack and kill %d civilians in sector %s.", + L"Transport group", + L"Transport group en route", }; STR16 gpGameClockString[] = @@ -6258,6 +6260,7 @@ STR16 z113FeaturesToggleText[] = L"Weather: Snow", L"Mini Events", L"Arulco Rebel Command", + L"Strategic Transport Groups", }; STR16 z113FeaturesHelpText[] = @@ -6305,6 +6308,7 @@ STR16 z113FeaturesHelpText[] = L"|W|e|a|t|h|e|r|: |S|n|o|w\nOverrides [Tactical Weather Settings] ALLOW_SNOW\n \nSnowstorms decrease visibility.\n \nConfigurable Options:\nSNOW_EVENTS_PER_DAY\nSNOW_CHANCE_PER_DAY\nSNOW_MIN_LENGTH_IN_MINUTES\nSNOW_MAX_LENGTH_IN_MINUTES\nWEAPON_RELIABILITY_REDUCTION_SNOW\nBREATH_GAIN_REDUCTION_SNOW\nVISUAL_DISTANCE_DECREASE_SNOW\nHEARING_REDUCTION_SNOW", L"|M|i|n|i |E|v|e|n|t|s\nOverrides [Mini Events Settings] MINI_EVENTS_ENABLED\n \nRandom events can occur.\n \nConfigurable Options:\nMINI_EVENTS_MIN_HOURS_BETWEEN_EVENTS\nMINI_EVENTS_MAX_HOURS_BETWEEN_EVENTS\n \nSee MiniEvents.lua for more details.", L"|A|R|C\nOverrides [Rebel Command Settings] REBEL_COMMAND_ENABLED\n \nCommand the rebel movement at the strategic level, and upgrade captured towns.\n \nFor tweakable values, see RebelCommand_Settings.ini.", + L"|S|t|r|a|t|e|g|i|c |T|r|a|n|s|p|o|r|t |G|r|o|u|p|s\nOverrides [Strategic Gameplay Settings] STRATEGIC_TRANSPORT_GROUPS_ENABLED\n \nTransport groups carry valuable equipment across the map.\n \nConfigurable Options:\nMAX_SIMULTANEOUS_STRATEGIC_TRANSPORT_GROUPS", }; STR16 z113FeaturesPanelText[] = @@ -6352,6 +6356,7 @@ STR16 z113FeaturesPanelText[] = L"Toggle snow. In a snowstorm, it is harder to see, weapons degrade faster, and it is a little harder to regain breath.", L"During the course of a campaign, brief events can pop up. You can select one of two responses, which may have positive and/or negative effects. Events can affect a wide variety of things, but mostly your mercs.", L"After completing the food delivery quest for the rebels, they will grant you access to their command website (A.R.C.). You can set the rebels' country-wide directive there, and capturing towns allows you to enact policies in that region that provide powerful bonuses. This comes at a price - town loyalty will rise slower, so you will need to work harder to have the locals trust you.", + L"The enemy sends groups across the map. If you can find and intercept them, they will probably have valuable gear. However, depending on your difficulty, each group that completes its transport mission provides the AI with strategic resources. Best experienced with Arulco Strategic Division enabled.", }; @@ -12093,6 +12098,8 @@ STR16 szRebelCommandAgentMissionsText[] = L"Coordinate efforts to find ways to sneak up on the enemy, but be careful: it's equally possible to put yourself in a disadvantaged deployment area. When attacking enemy forces, the deployment area is much larger.", L"Disrupt ASD", L"Wreak havoc on the day-to-day operations of the Arulco Special Division. Temporarily prevent the ASD from deploying additional mechanised units, and drastically reduce their daily income.", + L"Forge Transport Orders", + L"Create a bogus supply request. An enemy transport group will be ordered to rendezvous at this agent's location.", L"Strategic Intel", L"Intercept plans and discover where enemies intend to strike. When viewing teams on the strategic map, sectors prioritised by the enemy will be marked in red.", L"Improve Local Shops",