diff --git a/GameSettings.cpp b/GameSettings.cpp index 817dcf21..16cc6d29 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -1972,6 +1972,50 @@ void LoadGameExternalOptions() gGameExternalOptions.gfInvestigateSector = iniReader.ReadBoolean("Strategic Enemy AI Settings","ENEMY_INVESTIGATE_SECTOR",FALSE); gGameExternalOptions.gfReassignPendingReinforcements = iniReader.ReadBoolean("Strategic Enemy AI Settings","REASSIGN_PENDING_REINFORCEMENTS",TRUE); + // Flugente: Arulco special division + //################# Strategic Additional Enemy AI Settings ################## + + gGameExternalOptions.fASDActive = iniReader.ReadBoolean( "Strategic Additional Enemy AI Settings", "ASD_ACTIVE", FALSE ); + + gGameExternalOptions.usASDSupplyArrivalSector = iniReader.ReadInteger( "Strategic Additional Enemy AI Settings", "ASD_SUPPLY_ARRIVAL_SECTOR", 210, 0, 255 ); + + gGameExternalOptions.gASDResource_Cost[ASD_MONEY] = 1; + gGameExternalOptions.gASDResource_Cost[ASD_FUEL] = iniReader.ReadInteger( "Strategic Additional Enemy AI Settings", "ASD_COST_FUEL", 10, 1, 100 ); + gGameExternalOptions.gASDResource_Cost[ASD_HELI] = iniReader.ReadInteger( "Strategic Additional Enemy AI Settings", "ASD_COST_HELI", 100000, 10000, 1000000 ); + gGameExternalOptions.gASDResource_Cost[ASD_JEEP] = 50000; + gGameExternalOptions.gASDResource_Cost[ASD_TANK] = 200000; + + gGameExternalOptions.gASDResource_BuyTime[ASD_MONEY] = 0; + gGameExternalOptions.gASDResource_BuyTime[ASD_FUEL] = iniReader.ReadInteger( "Strategic Additional Enemy AI Settings", "ASD_TIME_FUEL", 60 * 8, 1, 60 * 48 ); + gGameExternalOptions.gASDResource_BuyTime[ASD_HELI] = iniReader.ReadInteger( "Strategic Additional Enemy AI Settings", "ASD_TIME_HELI", 60 * 20, 1, 60 * 48 ); + gGameExternalOptions.gASDResource_BuyTime[ASD_JEEP] = 60 * 12; + gGameExternalOptions.gASDResource_BuyTime[ASD_TANK] = 60 * 24; + + // Flugente: enemy heli + //################# Enemy Helicopter Settings ################## + + gGameExternalOptions.fEnemyHeliActive = iniReader.ReadBoolean( "Enemy Helicopter Settings", "ENEMYHELI_ACTIVE", TRUE ); + + gGameExternalOptions.gEnemyHeliMaxHP = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_HP", 100, 1, 500 ); + gGameExternalOptions.gEnemyHeliTimePerHPRepair = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_HP_REPAIRTIME", 6, 1, 20 ); + gGameExternalOptions.gEnemyHeliCostPerRepair1HP = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_HP_COST", 200, 10, 1000 ); + gGameExternalOptions.gEnemyHeliMaxFuel = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_FUEL", 50, 25, 200 ); + gGameExternalOptions.gEnemyHeliTimePerFuelRefuel = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_FUEL_REFUELTIME", 3, 1, 10 ); + + gGameExternalOptions.gEnemyHeliAllowedSAMContacts = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_TOLERATED_HOSTILE_SAMSECTORS", 4, 0, 10 ); + + gGameExternalOptions.gEnemyHeliSAMDamage_Base = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_SAM_DAMAGE_BASE", 25, 10, 100 ); + gGameExternalOptions.gEnemyHeliSAMDamage_Var = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_SAM_DAMAGE_VAR", 30, 10, 100 ); + gGameExternalOptions.gEnemyHeliMANPADSDamage_Base = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_MANPADS_DAMAGE_BASE", 40, 10, 100 ); + gGameExternalOptions.gEnemyHeliMANPADSDamage_Var = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_MANPADS_DAMAGE_VAR", 80, 10, 100 ); + + gGameExternalOptions.usEnemyHeliBaseSector = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_BASE", 210, 0, 255 ); + + gGameExternalOptions.sEnemyHeliBaseParkGridno[0] = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_BASE_PARKPOSITION_1", 18475, -1, 2147483647 ); + gGameExternalOptions.sEnemyHeliBaseParkGridno[1] = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_BASE_PARKPOSITION_2", 18469, -1, 2147483647 ); + gGameExternalOptions.sEnemyHeliBaseParkTileIndex = iniReader.ReadInteger( "Enemy Helicopter Settings", "ENEMYHELI_BASE_PARK_TILEINDEX", 1689, -1, 50000 ); + + //################# Militia Training Settings ################## gGameExternalOptions.iMaxMilitiaPerSector = iniReader.ReadInteger("Militia Training Settings","MAX_MILITIA_PER_SECTOR",20, 1, CODE_MAXIMUM_NUMBER_OF_REBELS); diff --git a/GameSettings.h b/GameSettings.h index d2f4299f..7786bc04 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -212,6 +212,18 @@ enum PRISONER_INTERROGATION_MAX }; +// Flugente: ASD resource types +enum +{ + ASD_MONEY, + ASD_FUEL, + ASD_HELI, + ASD_JEEP, + ASD_TANK, + + ASD_RESOURCE_MAX +}; + typedef struct { BOOLEAN fGunNut; @@ -600,6 +612,33 @@ typedef struct BOOLEAN gfInvestigateSector; BOOLEAN gfReassignPendingReinforcements; + // Flugente: ASD + BOOLEAN fASDActive; + UINT8 usASDSupplyArrivalSector; + INT32 gASDResource_Cost[ASD_RESOURCE_MAX]; + INT32 gASDResource_BuyTime[ASD_RESOURCE_MAX]; + + // Flugente: enemy heli + BOOLEAN fEnemyHeliActive; + + UINT16 gEnemyHeliMaxHP; + UINT16 gEnemyHeliTimePerHPRepair; + INT32 gEnemyHeliCostPerRepair1HP; + UINT16 gEnemyHeliMaxFuel; + UINT16 gEnemyHeliTimePerFuelRefuel; + + UINT8 gEnemyHeliAllowedSAMContacts; + UINT16 gEnemyHeliSAMDamage_Base; + UINT16 gEnemyHeliSAMDamage_Var; + UINT16 gEnemyHeliMANPADSDamage_Base; + UINT16 gEnemyHeliMANPADSDamage_Var; + + UINT8 usEnemyHeliBaseSector; + INT32 sEnemyHeliBaseParkGridno[2]; + INT32 sEnemyHeliBaseParkTileIndex; + + + INT32 ubEnemiesItemDrop; BOOLEAN gfUseExternalLoadscreens; diff --git a/GameVersion.h b/GameVersion.h index ac942d9d..20e70c53 100644 --- a/GameVersion.h +++ b/GameVersion.h @@ -21,6 +21,7 @@ extern CHAR16 zTrackingNumber[16]; // Keeps track of the saved game version. Increment the saved game version whenever // you will invalidate the saved game file +#define ENEMY_HELICOPTERS 164 // Flugente: enemy helicopters #define DRUG_SYSTEM_REDONE 163 // Flugente: reworked the drug system #define MILITIA_PATH_PLOTTING 162 // Flugente: the player can plot militia paths in strategic, similar to player or helicopter travel #define STRATEGIC_TEAM_GROUPS 161 // Flugente: a change to the GROUP-structure allows any team to have strategic movement groups @@ -82,7 +83,7 @@ extern CHAR16 zTrackingNumber[16]; #define AP100_SAVEGAME_DATATYPE_CHANGE 105 // Before this, we didn't have the 100AP structure changes #define NIV_SAVEGAME_DATATYPE_CHANGE 102 // Before this, we used the old structure system -#define SAVE_GAME_VERSION DRUG_SYSTEM_REDONE +#define SAVE_GAME_VERSION ENEMY_HELICOPTERS //#define RUSSIANGOLD #ifdef __cplusplus diff --git a/Laptop/finances.cpp b/Laptop/finances.cpp index 1273892e..c508df1a 100644 --- a/Laptop/finances.cpp +++ b/Laptop/finances.cpp @@ -1576,28 +1576,13 @@ void ProcessTransactionString(STR16 pString, FinanceUnitPtr pFinance) break; // HEADROCK HAM 3.6: Paid for Facility Use - case( FACILITY_OPERATIONS ): - swprintf(pString, L"%s", pTransactionText[ FACILITY_OPERATIONS ]); - break; - - // HEADROCK HAM 3.6: Paid for militia upkeep - case( MILITIA_UPKEEP ): - swprintf(pString, L"%s", pTransactionText[ MILITIA_UPKEEP ]); - break; - - // Flugente: ransom from released prisoners + case FACILITY_OPERATIONS: + case MILITIA_UPKEEP: case PRISONER_RANSOM: - swprintf(pString, L"%s", pTransactionText[ PRISONER_RANSOM ]); - break; - - // Flugente: disease: subscription to WHO data case WHO_SUBSCRIPTION: - swprintf( pString, L"%s", pTransactionText[WHO_SUBSCRIPTION] ); - break; - - // Flugente: a contract with a PMC case PMC_CONTRACT: - swprintf( pString, L"%s", pTransactionText[PMC_CONTRACT] ); + case SAM_REPAIR: + swprintf( pString, L"%s", pTransactionText[pFinance->ubCode] ); break; } } diff --git a/Laptop/finances.h b/Laptop/finances.h index d77a362a..3b429d3f 100644 --- a/Laptop/finances.h +++ b/Laptop/finances.h @@ -56,6 +56,7 @@ enum PRISONER_RANSOM, // Flugente: ransom for released prisoners WHO_SUBSCRIPTION, // Flugente: subscription do WHO data PMC_CONTRACT, // Flugente: hired militia from a PMC + SAM_REPAIR, // Flugente: repair SAM site TEXT_NUM_FINCANCES }; diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index 54bd23b4..aef88b51 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -116,6 +116,7 @@ #include "CampaignStats.h" // added by Flugente #include "DynamicDialogue.h" // added by Flugente #include "PMC.h" // added by Flugente + #include "ASD.h" // added by Flugente #endif #include "BobbyR.h" @@ -4398,6 +4399,13 @@ BOOLEAN SaveGame( int ubSaveGameID, STR16 pGameDesc ) goto FAILED_TO_SAVE; } + // Flugente: enemy helicopters + if ( !SaveASDData( hFile ) ) + { + ScreenMsg( FONT_MCOLOR_WHITE, MSG_ERROR, L"ERROR writing Arulco special division data" ); + goto FAILED_TO_SAVE; + } + //Close the saved game file FileClose( hFile ); @@ -6097,6 +6105,26 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) return(FALSE); } + if ( guiCurrentSaveGameVersion >= ENEMY_HELICOPTERS ) + { + uiRelEndPerc += 1; + SetRelativeStartAndEndPercentage( 0, uiRelStartPerc, uiRelEndPerc, L"Load Arulco special division data..." ); + RenderProgressBar( 0, 100 ); + uiRelStartPerc = uiRelEndPerc; + + if ( !LoadASDData( hFile ) ) + { + DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String( "Arulco special division data Load failed" ) ); + FileClose( hFile ); + return(FALSE); + } + } + else + { + // if loading an old savegame that did not have this feature, initialise + InitASD(); + } + // //Close the saved game file // diff --git a/Strategic/ASD.cpp b/Strategic/ASD.cpp new file mode 100644 index 00000000..e66176df --- /dev/null +++ b/Strategic/ASD.cpp @@ -0,0 +1,1425 @@ +/** +* @file +* @author Flugente (bears-pit.com) +*/ + +#ifdef PRECOMPILEDHEADERS +#include "Laptop All.h" +#else +#include "ASD.h" + +#include "strategic.h" +#include "WCheck.h" +#include "Utilities.h" +#include "WordWrap.h" +#include "Cursors.h" +#include "Line.h" +#include "Insurance Text.h" +#include "Encrypted File.h" +#include "Text.h" +#include "Multi Language Graphic Utils.h" +#include "random.h" +#include "Interface.h" +#include "Soldier Add.h" +#include "Soldier Profile.h" +#include "Overhead.h" +#include "Map Screen Interface.h" +#include "finances.h" +#include "Game Clock.h" +#include "SaveLoadGame.h" +#include "GameVersion.h" +#include "message.h" +#include "Game Event Hook.h" +#include "militia control.h" +#include "Town Militia.h" +#include "Strategic Town Loyalty.h" +#include "map information.h" + +#include "Campaign.h" +#include "random.h" +#include "Explosion Control.h" +#include "Strategic AI.h" +#include "Strategic Mines.h" +#include "Reinforcement.h" +#include "Player Command.h" +#include "Queen Command.h" +#include "Map Screen Helicopter.h" +#include "Points.h" +#include "worldman.h" +#include "Map Information.h" +#include "opplist.h" +#include "Dialogue Control.h" +#include "Sound Control.h" +#include "renderworld.h" +#include "Isometric Utils.h" +#endif + +extern INT32 ReadFieldByField( HWFILE hFile, PTR pDest, UINT32 uiFieldSize, UINT32 uiElementSize, UINT32 uiCurByteCount ); + +extern INT32 giReinforcementPool; + +extern BOOLEAN gfTacticalDoHeliRun; + +extern UINT8 ubSAMControlledSectors[MAP_WORLD_Y][MAP_WORLD_X]; + +// Arulco special division decision code + +// ASD savegame variables + +// resources +INT32 gASDResource[ASD_RESOURCE_MAX]; + +// remember which resources we've already bought but haven't received yet +INT32 gASDResource_Ordered[ASD_RESOURCE_MAX]; + +UINT32 gASD_Flags = 0; + +// enemy heli constants +#define ENEMYHELI_DESTROYED 0x00000001 +#define ENEMYHELI_ORDER_GOTODESTINATION 0x00000002 +#define ENEMYHELI_KNOWNTOPLAYER 0x00000008 // the player knows about this heli (it will this be displayed on the map) + +#define ENEMYHELI_ORDER_DROPTROOPS 0x00000010 +#define ENEMYHELI_ORDER_PICKUPTROOPS 0x00000020 +#define ENEMYHELI_REPAIRING 0x00000040 // we are currently repairing +#define ENEMYHELI_REFUELING 0x00000080 + +#define ENEMYHELI_SHOTDOWN 0x00000100 // heli was destroyed by a SAM (instead of being bombed on the ground etc.) + +#define ENEMYHELI_ORDERS (ENEMYHELI_ORDER_GOTODESTINATION|ENEMYHELI_ORDER_DROPTROOPS|ENEMYHELI_ORDER_PICKUPTROOPS|ENEMYHELI_REPAIRING|ENEMYHELI_REFUELING) + +#define ENEMYHELI_MAX 2 + +// for now, this stays fixed +UINT32 gEnemyHeliTravelTimePerSector = 10; +UINT8 gEnemyHeliMaxTroops = 6; + +std::vector gEnemyHeliVector; + +void InitASD( ) +{ + gASDResource[ASD_MONEY] = 100000; + gASDResource[ASD_FUEL] = 0; + gASDResource[ASD_HELI] = 0; + gASDResource[ASD_JEEP] = 2; + gASDResource[ASD_TANK] = 0; + + gASDResource_Ordered[ASD_MONEY] = 0; + gASDResource_Ordered[ASD_FUEL] = 0; + gASDResource_Ordered[ASD_HELI] = 0; + gASDResource_Ordered[ASD_JEEP] = 0; + gASDResource_Ordered[ASD_TANK] = 0; + + gASD_Flags = 0; + + EnemyHeliInit( ); +} + +BOOLEAN SaveASDData( HWFILE hwFile ) +{ + UINT32 uiNumBytesWritten = 0; + + // ASD data + for ( int i = ASD_MONEY; i < ASD_RESOURCE_MAX; ++i ) + { + if ( !FileWrite( hwFile, &gASDResource[i], sizeof(INT32), &uiNumBytesWritten ) ) return(FALSE); + } + + for ( int i = ASD_MONEY; i < ASD_RESOURCE_MAX; ++i ) + { + if ( !FileWrite( hwFile, &gASDResource_Ordered[i], sizeof(INT32), &uiNumBytesWritten ) ) return(FALSE); + } + + if ( !FileWrite( hwFile, &gASD_Flags, sizeof(UINT32), &uiNumBytesWritten ) ) return(FALSE); + + // in order to save a vector, we first save its size and then its content + UINT32 size = gEnemyHeliVector.size( ); + if ( !FileWrite( hwFile, &size, sizeof(UINT32), &uiNumBytesWritten ) ) + return(FALSE); + + for ( UINT32 i = 0; i < size; ++i ) + { + if ( !FileWrite( hwFile, &gEnemyHeliVector[i], sizeof(ENEMY_HELI), &uiNumBytesWritten ) ) + return(FALSE); + } + + return TRUE; +} + +BOOLEAN LoadASDData( HWFILE hwFile ) +{ + UINT32 numBytesRead = 0; + + // ASD data + for ( int i = ASD_MONEY; i < ASD_RESOURCE_MAX; ++i ) + { + numBytesRead = ReadFieldByField( hwFile, &gASDResource[i], sizeof(gASDResource[i]), sizeof(INT32), numBytesRead ); + } + + for ( int i = ASD_MONEY; i < ASD_RESOURCE_MAX; ++i ) + { + numBytesRead = ReadFieldByField( hwFile, &gASDResource_Ordered[i], sizeof(gASDResource_Ordered[i]), sizeof(INT32), numBytesRead ); + } + + numBytesRead = ReadFieldByField( hwFile, &gASD_Flags, sizeof(gASD_Flags), sizeof(UINT32), numBytesRead ); + + // clear any artefacts prior to loading + EnemyHeliInit( ); + + UINT32 size = 0; + numBytesRead = ReadFieldByField( hwFile, &size, sizeof(size), sizeof(UINT32), numBytesRead ); + + for ( UINT32 i = 0; i < size; ++i ) + { + ENEMY_HELI data; + numBytesRead = ReadFieldByField( hwFile, &data, sizeof(data), sizeof(ENEMY_HELI), 0 ); + + gEnemyHeliVector.push_back( data ); + } + + return TRUE; +} + +// when called, evaluate the current situation, and perform actions to harm the player if necessary +void UpdateASD( ) +{ + // if feature is not active, nothing will happen + if ( !gGameExternalOptions.fASDActive ) + return; + + if ( DidGameJustStart( ) ) + return; + + // determine whether we need to buy new toys + ASDDecideOnPurchases(); + + // decide how to use helicopters + ASDDecideHeliOperations(); +} + +void ASDDecideOnPurchases() +{ + UINT32 needed_fuel = 0; + UINT32 needed_heli = 0; + UINT32 needed_tank = 0; + UINT32 needed_jeep = 0; + + UINT16 highestplayerprogress = HighestPlayerProgressPercentage( ); + + // do we need new helis? + if ( gASD_Flags & ASDFACT_HELI_UNLOCKED && gGameExternalOptions.fEnemyHeliActive ) + { + // determine number of SAMs under AI control + UINT16 numenemysams = 0; + + for ( UINT16 x = 0; x < MAX_NUMBER_OF_SAMS; ++x ) + { + StrategicMapElement *pSAMStrategicMap = &(StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX( pSamList[x] )]); + + if ( pSamList[x] && pSAMStrategicMap && pSAMStrategicMap->fEnemyControlled ) + ++numenemysams; + } + + // only acquire/replace helis if there is still airspace the AI controls + if ( numenemysams ) + { + UINT32 activehelis = 0; + + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != gEnemyHeliVector.end( ); ++it ) + { + if ( !((*it).flagmask & ENEMYHELI_DESTROYED) ) + ++activehelis; + } + + needed_heli = ENEMYHELI_MAX - activehelis; + + // for any heli, we need fuel + needed_fuel += (activehelis + needed_heli) * gGameExternalOptions.gEnemyHeliMaxFuel; + } + } + + // do we need new jeeps? + if ( gASD_Flags & ASDFACT_JEEP_UNLOCKED ) + { + // how many would we like to have? Note that this refers to the ones in our pool, the vehicles awarded to enemy groups are no longer accounted for + needed_jeep = min( 10, 2 + highestplayerprogress / 10 ); + + // if we already unlocked tanks, we want less jeeps + if ( gASD_Flags & ASDFACT_TANK_UNLOCKED ) + { + needed_jeep = max( 4, 12 - highestplayerprogress / 10 ); + } + + // how many are needed? + needed_jeep = max( 0, needed_jeep - gASDResource[ASD_JEEP] ); + + needed_fuel += needed_jeep * ASDResourceCostFuel( ASD_JEEP ); + } + + // do we need new tanks? + if ( gASD_Flags & ASDFACT_TANK_UNLOCKED ) + { + // how many would we like to have? Note that this refers to the ones in our pool, the vehicles awarded to enemy groups are no longer accounted for + needed_tank = min( 5, highestplayerprogress / 20 ); + + // how many are needed? + needed_tank = max( 0, needed_tank - gASDResource[ASD_TANK] ); + + needed_fuel += needed_tank * ASDResourceCostFuel( ASD_TANK ); + } + + // how much new fuel do we need? + needed_fuel = max( 0, needed_fuel - gASDResource[ASD_FUEL] ); + + // reduce order by what we'e alreay ordered + needed_fuel = max( 0, needed_fuel - gASDResource_Ordered[ASD_FUEL] ); + needed_jeep = max( 0, needed_jeep - gASDResource_Ordered[ASD_JEEP] ); + needed_tank = max( 0, needed_tank - gASDResource_Ordered[ASD_TANK] ); + needed_heli = max( 0, needed_heli - gASDResource_Ordered[ASD_HELI] ); + + // check whether we can afford all new purchases. If we can't, lower your expectations + while ( needed_fuel * ASDResourceCostMoney( ASD_FUEL ) + + needed_jeep * ASDResourceCostMoney( ASD_JEEP ) + + needed_tank * ASDResourceCostMoney( ASD_TANK ) + + needed_heli * ASDResourceCostMoney( ASD_HELI ) > gASDResource[ASD_MONEY] ) + { + if ( needed_tank ) --needed_tank; + else if ( needed_heli ) --needed_heli; + else if ( needed_jeep ) --needed_jeep; + else needed_fuel = max( 0, needed_fuel - 10 ); + } + + // purchase things + if ( needed_fuel ) + { + AddStrategicAIResources( ASD_MONEY, -needed_fuel * ASDResourceCostMoney( ASD_FUEL ) ); + + gASDResource_Ordered[ASD_FUEL] += needed_fuel; + + AddStrategicEvent( EVENT_ASD_PURCHASE_FUEL, GetWorldTotalMin( ) + ASDResourceDeliveryTime( ASD_FUEL ), needed_fuel ); + } + + if ( needed_jeep ) + { + AddStrategicAIResources( ASD_MONEY, -needed_jeep * ASDResourceCostMoney( ASD_JEEP ) ); + AddStrategicAIResources( ASD_FUEL, -needed_jeep * ASDResourceCostFuel( ASD_JEEP ) ); + + gASDResource_Ordered[ASD_JEEP] += needed_jeep; + + AddStrategicEvent( EVENT_ASD_PURCHASE_JEEP, GetWorldTotalMin( ) + ASDResourceDeliveryTime( ASD_JEEP ), needed_jeep ); + } + + if ( needed_tank ) + { + AddStrategicAIResources( ASD_MONEY, -needed_tank * ASDResourceCostMoney( ASD_TANK ) ); + AddStrategicAIResources( ASD_FUEL, -needed_tank * ASDResourceCostFuel( ASD_TANK ) ); + + gASDResource_Ordered[ASD_TANK] += needed_tank; + + AddStrategicEvent( EVENT_ASD_PURCHASE_TANK, GetWorldTotalMin( ) + ASDResourceDeliveryTime( ASD_TANK ), needed_tank ); + } + + if ( needed_heli ) + { + AddStrategicAIResources( ASD_MONEY, -needed_heli * ASDResourceCostMoney( ASD_HELI ) ); + + gASDResource_Ordered[ASD_HELI] += needed_heli; + + AddStrategicEvent( EVENT_ASD_PURCHASE_HELI, GetWorldTotalMin( ) + ASDResourceDeliveryTime( ASD_HELI ), needed_heli ); + } +} + +void ASDDecideHeliOperations() +{ + if ( !gGameExternalOptions.fEnemyHeliActive ) + return; + + INT16 id = -1; + + // loop over all helis and, if they are idle, give them orders + std::vector::iterator itend = gEnemyHeliVector.end( ); + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != itend; ++it ) + { + ++id; + + if ( (*it).flagmask & ENEMYHELI_DESTROYED ) + continue; + + if ( (*it).flagmask & ENEMYHELI_ORDERS ) + continue; + + // safety: make sure heli has fuel and is repaired (player might have sabotaged us...) + UpdateEnemyHeli( id ); + + if ( (*it).flagmask & ENEMYHELI_ORDERS ) + continue; + + ENEMY_HELI& heli = (*it); + + UINT32 totalrating = 0; + std::vector< std::pair > ratedhelioperations; + + // build a list of all possible locations that require us to drop troops in + for ( UINT8 sX = 1; sX < MAP_WORLD_X - 1; ++sX ) + { + for ( UINT8 sY = 1; sY < MAP_WORLD_X - 1; ++sY ) + { + // not if the enemy controls this sector + if ( StrategicMap[CALCULATE_STRATEGIC_INDEX( sX, sY )].fEnemyControlled ) + continue; + + UINT8 sector = SECTOR( sX, sY ); + + // if the player has never been here (or if the grace period is not over), this is no valid target + if ( !OkayForEnemyToMoveThroughSector( sector ) ) + continue; + + // if this is already the target of another helicopter, ignore it + BOOLEAN fOtherHeliAlreadyAssigned = FALSE; + std::vector::iterator itend = gEnemyHeliVector.end( ); + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != itend; ++it ) + { + if ( (*it).flagmask & ENEMYHELI_DESTROYED ) + continue; + + if ( (*it).flagmask & (ENEMYHELI_ORDER_GOTODESTINATION) && (*it).sector_destination == sector ) + { + fOtherHeliAlreadyAssigned = TRUE; + break; + } + } + + if ( fOtherHeliAlreadyAssigned ) + continue; + + SECTORINFO *pSectorInfo = &(SectorInfo[sector]); + + if ( !pSectorInfo ) + continue; + + // if we would run out of fuel while doing this, don't + if ( 2 * (abs( SECTORX( heli.sector_current ) - SECTORX( sector ) ) + abs( SECTORY( heli.sector_current ) - SECTORY( sector ) )) > heli.fuel ) + continue; + + // if number of SAM contacts is too high, don't consider this sector + if ( 2 * heli.SetHeliFlightPath( SECTOR( sX, sY ) ) > gGameExternalOptions.gEnemyHeliAllowedSAMContacts ) + continue; + + INT16 rating = 0; // how important we deem attacking this sector + + // on a road, we could set up a roadblock. + if ( IsSectorRoad( sX, sY ) ) + rating += 2; + + // if volunteer feature is on, farm sectors are a valid target, as they increase the rate with which the player gains volunteers + if ( gGameExternalOptions.fMilitiaVolunteerPool && IsSectorFarm( sX, sY ) ) + rating += 5; + + // town sectors are worth more + if ( StrategicMap[CALCULATE_STRATEGIC_INDEX( sX, sY )].bNameId != BLANK_SECTOR ) + rating += 10; + + // if we take a mine, we cut the player's income - very juicy + if ( IsThereAMineInThisSector( sX, sY ) ) + rating += 20; + + // if this is a player-owned SAM, it is very important that we take this. Should the SAM be operational, we will likely have ignore this tdue to the earlier check for SAM contacts + for ( UINT16 x = 0; x < MAX_NUMBER_OF_SAMS; ++x ) + { + if ( pSamList[x] == sector ) + { + rating += 20; + + break; + } + } + + // lower a sector's rating for the amount of militia and mercs here - no sense in dropping a squad into a killing field + // for now, the AI simply knows the player's amount of troops - later on, try to rely on espionage + UINT16 num_militia = CountAllMilitiaInFiveSectors( sX, sY ); + UINT16 num_mercs = PlayerMercsInSector( sX, sY, 0 ); + if ( num_militia + num_mercs ) + { + if ( num_militia + num_mercs > 2 * gEnemyHeliMaxTroops ) + rating = 0; + else if ( num_militia + num_mercs > gEnemyHeliMaxTroops ) + rating /= 2; + // only a very small garrison - we can wipe them out with no risk and add losses to the player. We should totally do that! + else if ( num_militia + num_mercs < gEnemyHeliMaxTroops ) + rating *= 1.5f; + } + + if ( rating > 0 ) + { + totalrating += rating; + ratedhelioperations.push_back( std::make_pair( sector, rating ) ); + } + } + } + + // for safety, delete set flight data + heli.sector_destination = 0; + heli.sector_waypoint = 0; + heli.flagmask &= ~ENEMYHELI_ORDER_GOTODESTINATION; + + // if there are viable operations, pick one at random (weighted by rating) and start that operation + if ( totalrating && !ratedhelioperations.empty( ) ) + { + UINT32 pickedrating = Random( totalrating ); + UINT32 ratingsofar = 0; + + for ( std::vector< std::pair >::iterator it = ratedhelioperations.begin( ); it != ratedhelioperations.end( ); ++it ) + { + if ( pickedrating <= ratingsofar + (*it).second ) + { + // attack this sector! + heli.SetHeliFlightPath( (*it).first ); + + heli.troopcount = gEnemyHeliMaxTroops; + giReinforcementPool -= gEnemyHeliMaxTroops; + heli.flagmask |= ENEMYHELI_ORDER_DROPTROOPS; + + AddStrategicEvent( EVENT_ENEMY_HELI_UPDATE, GetWorldTotalMin( ) + gEnemyHeliTravelTimePerSector, id ); + + break; + } + + ratingsofar += (*it).second; + } + } + } +} + +void SetASDFlag( UINT32 aFlag ) +{ + gASD_Flags |= aFlag; +} + +UINT32 ASDResourceDeliveryTime( UINT8 aType ) +{ + return gGameExternalOptions.gASDResource_BuyTime[aType]; +} + +UINT32 ASDResourceCostMoney( UINT8 aType ) +{ + return gGameExternalOptions.gASDResource_Cost[aType]; +} + +// add resources to the AIs resource pool +void AddStrategicAIResources( UINT8 aType, INT32 aAmount ) +{ + gASDResource[aType] = max( 0, gASDResource[aType] + aAmount ); + + if ( aType == ASD_HELI ) + AddNewHeli( ); +} + +void ASDReceiveOrderedStrategicAIResources(UINT8 aType, INT32 aAmount) +{ + gASDResource_Ordered[aType] = max( 0, gASDResource_Ordered[aType] - aAmount ); + + AddStrategicAIResources( aType, aAmount ); +} + +//////////////////////////////////////////// enemy helis ///////////////////////////////////////////////////////// + +// determine best heli flight path (minimising contact to hostile SAM sectors) and return number of possible SAM contacts +UINT8 ENEMY_HELI::SetHeliFlightPath( UINT8 aDest ) +{ + sector_destination = aDest; + flagmask |= ENEMYHELI_ORDER_GOTODESTINATION; + + // determine best waypoint + // standard way of pathing is to minimise the max deviation of x and y + // we are allowed to set one intermediate waypoint + // For now, we try 3 methods: + // 1. no waypoint (waypoint equals destination) + // 2. waypoint is x_start, y_dest + // 3. waypoint is x_dest, y_start + // we pick the waypoint whose path has the least amount of hostile SAM sectors. + + UINT8 best_samcontacts = 0; + UINT8 tmpsect = sector_current; + UINT8 tmpcontacts = 0; + + // 1. no waypoint (waypoint equals destination) + best_samcontacts = NumPlayerAirSpaceOnHeliPath( sector_current, sector_destination ); + sector_waypoint = sector_destination; + + // 2. waypoint is x_start, y_dest + tmpsect = SECTOR( SECTORX( sector_current ), SECTORY( sector_destination ) ); + tmpcontacts = NumPlayerAirSpaceOnHeliPath( sector_current, tmpsect ) + NumPlayerAirSpaceOnHeliPath( tmpsect, sector_destination ); + if ( tmpcontacts < best_samcontacts ) + { + best_samcontacts = tmpcontacts; + sector_waypoint = tmpsect; + } + + // 3. waypoint is x_dest, y_start + tmpsect = SECTOR( SECTORX( sector_destination ), SECTORY( sector_current ) ); + tmpcontacts = NumPlayerAirSpaceOnHeliPath( sector_current, tmpsect ) + NumPlayerAirSpaceOnHeliPath( tmpsect, sector_destination ); + if ( tmpcontacts < best_samcontacts ) + { + best_samcontacts = tmpcontacts; + sector_waypoint = tmpsect; + } + + return best_samcontacts; +} + +void ENEMY_HELI::Destroy( ) +{ + troopcount = 0; + hp = 0; + fuel = 0; + + flagmask |= ENEMYHELI_DESTROYED; + + // if the player knows about this heli, play a sound and leave a message + if ( flagmask & ENEMYHELI_KNOWNTOPLAYER ) + { + CHAR16 pStr2[128]; + GetSectorIDString( SECTORX( sector_current ), SECTORY( sector_current ), 0, pStr2, FALSE ); + + MapScreenMessage( FONT_MCOLOR_LTRED, MSG_INTERFACE, szEnemyHeliText[0], pStr2 ); + + // if heli was shot down by a SAM, play a crashing sound + if ( flagmask & ENEMYHELI_SHOTDOWN ) + { + PlayJA2StreamingSampleFromFile( "stsounds\\blah2.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN, NULL ); + } + } + + // if a helicopter was shot down, remember that for the sector - we might spawn a downed pilot for the player to search! + if ( flagmask & ENEMYHELI_SHOTDOWN ) + { + SECTORINFO *pSectorInfo = &(SectorInfo[sector_current]); + + if ( pSectorInfo ) + { + pSectorInfo->usSectorInfoFlag |= SECTORINFO_ENEMYHELI_SHOTDOWN; + } + } +} + +void EnemyHeliInit( ) +{ + gEnemyHeliVector.clear( ); +} + + +void AddNewHeli( ) +{ + while ( gASDResource[ASD_HELI] > 0 ) + { + --gASDResource[ASD_HELI]; + + BOOLEAN fHeliReplaced = FALSE; + + INT16 id = -1; + + // if a heli was destroyed, replace it + std::vector::iterator itend = gEnemyHeliVector.end( ); + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != itend; ++it ) + { + ++id; + + if ( (*it).flagmask & ENEMYHELI_DESTROYED ) + { + (*it).flagmask = 0; + (*it).sector_home = gGameExternalOptions.usEnemyHeliBaseSector; + (*it).sector_destination = 0; + (*it).sector_waypoint = 0; + (*it).sector_current = (*it).sector_home; + (*it).troopcount = 0; + (*it).hp = gGameExternalOptions.gEnemyHeliMaxHP; + (*it).fuel = 0; + + UpdateEnemyHeli( id ); + + fHeliReplaced = TRUE; + + break; + } + } + + // otherwise, create a new one + if ( !fHeliReplaced ) + { + ENEMY_HELI data; + + data.flagmask = 0; + data.sector_home = gGameExternalOptions.usEnemyHeliBaseSector; + data.sector_destination = 0; + data.sector_waypoint = 0; + data.sector_current = data.sector_home; + data.troopcount = 0; + data.hp = gGameExternalOptions.gEnemyHeliMaxHP; + data.fuel = 0; + + gEnemyHeliVector.push_back( data ); + + ++id; + + UpdateEnemyHeli( id ); + } + } +} + +// all enemy helis carrying troops in this sector drop these into combat, and get the order to head home +void EnemyHeliTroopDrop( UINT8 aSector ) +{ + INT16 id = -1; + + std::vector::iterator itend = gEnemyHeliVector.end( ); + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != itend; ++it ) + { + ++id; + + if ( (*it).sector_current != aSector ) + continue; + + if ( (*it).flagmask & ENEMYHELI_DESTROYED ) + continue; + + if ( !(*it).troopcount ) + continue; + + SectorInfo[aSector].ubNumElites = min( 255, SectorInfo[aSector].ubNumElites + (*it).troopcount ); + + (*it).troopcount = 0; + + (*it).flagmask &= ~ENEMYHELI_ORDER_DROPTROOPS; + + UpdateEnemyHeli( id ); + } +} + +std::set GetEnemyHeliSectors( BOOLEAN afKnownToPlayer ) +{ + std::set set; + + std::vector::iterator itend = gEnemyHeliVector.end( ); + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != itend; ++it ) + { + if ( (*it).flagmask & ENEMYHELI_DESTROYED ) + continue; + + // if the player doesn't know about this heli, don't add this + if ( afKnownToPlayer && !((*it).flagmask & ENEMYHELI_KNOWNTOPLAYER) ) + continue; + + set.insert( (*it).sector_current ); + } + + return set; +} + +void UpdateEnemyHeliRepair( INT16 id ) +{ + if ( id >= 0 && id < gEnemyHeliVector.size( ) ) + { + ENEMY_HELI& heli = gEnemyHeliVector[id]; + + // if the heli is already destroyed, nothing to do here + if ( heli.flagmask & ENEMYHELI_DESTROYED ) + return; + + // update + if ( heli.flagmask & ENEMYHELI_REPAIRING ) + { + heli.flagmask &= ~ENEMYHELI_REPAIRING; + + if ( heli.sector_current == heli.sector_home ) + { + INT32 cost = (gGameExternalOptions.gEnemyHeliMaxHP - heli.hp) * gGameExternalOptions.gEnemyHeliCostPerRepair1HP; + + AddStrategicAIResources( ASD_MONEY, -cost ); + + heli.hp = gGameExternalOptions.gEnemyHeliMaxHP; + } + } + + UpdateEnemyHeli( id ); + } +} + +void UpdateEnemyHeliRefuel( INT16 id ) +{ + if ( id >= 0 && id < gEnemyHeliVector.size( ) ) + { + ENEMY_HELI& heli = gEnemyHeliVector[id]; + + // if the heli is already destroyed, nothing to do here + if ( heli.flagmask & ENEMYHELI_DESTROYED ) + return; + + // update + if ( heli.flagmask & ENEMYHELI_REFUELING ) + { + heli.flagmask &= ~ENEMYHELI_REFUELING; + + if ( heli.sector_current == heli.sector_home ) + { + UINT8 addedfuel = min( gASDResource[ASD_FUEL], gGameExternalOptions.gEnemyHeliMaxFuel - heli.fuel ); + + AddStrategicAIResources( ASD_FUEL, -addedfuel ); + + heli.fuel += addedfuel; + + // if heli isn't full, try again + if ( heli.fuel < gGameExternalOptions.gEnemyHeliMaxFuel ) + { + heli.flagmask |= ENEMYHELI_REFUELING; + + AddStrategicEvent( EVENT_ENEMY_HELI_REFUEL, GetWorldTotalMin( ) + (gGameExternalOptions.gEnemyHeliMaxFuel - heli.fuel) * gGameExternalOptions.gEnemyHeliTimePerFuelRefuel, id ); + + return; + } + } + } + + UpdateEnemyHeli( id ); + } +} + +void UpdateEnemyHeli( INT16 id ) +{ + if ( id >= 0 && id < gEnemyHeliVector.size( ) ) + { + ENEMY_HELI& heli = gEnemyHeliVector[id]; + + // if the heli is already destroyed, nothing to do here + if ( heli.flagmask & ENEMYHELI_DESTROYED ) + return; + + if ( heli.flagmask & ENEMYHELI_ORDER_GOTODESTINATION ) + { + if ( heli.sector_current == heli.sector_waypoint ) + heli.sector_waypoint = heli.sector_destination; + + heli.sector_current = GetNextEnemyHeliSector( heli.sector_current, heli.sector_waypoint ); + + if ( heli.fuel ) + --heli.fuel; + else + { + // if the heli is in the air without fuel, it will crash! + heli.flagmask |= ENEMYHELI_SHOTDOWN; + + heli.Destroy(); + + return; + } + + // whenever we arrive at a new sector, SAM batteries get a chance to shoot at a heli + EnemyHeliCheckPlayerKnowledge( id ); + EnemyHeliSAMCheck( id ); + + if ( heli.sector_current == heli.sector_destination ) + { + heli.sector_waypoint = 0; + heli.flagmask &= ~ENEMYHELI_ORDER_GOTODESTINATION; + } + } + + // if the heli is already destroyed, nothing to do here + if ( heli.flagmask & ENEMYHELI_DESTROYED ) + return; + + // each time we update a heli, check whether we need repairs or refueling + BOOLEAN needrepair = FALSE; + BOOLEAN needrefuel = FALSE; + if ( heli.hp < gGameExternalOptions.gEnemyHeliMaxHP ) + { + needrepair = TRUE; + + if ( !heli.hp ) + { + heli.Destroy( ); + + return; + } + } + + if ( heli.fuel < gGameExternalOptions.gEnemyHeliMaxFuel ) + { + needrefuel = TRUE; + } + + // if we need repair or refueling and we are at the base, do that + if ( needrepair || needrefuel ) + { + if ( heli.sector_current == heli.sector_home ) + { + if ( needrepair ) + { + // determine how long this will take + INT32 time = (gGameExternalOptions.gEnemyHeliMaxHP - heli.hp) * gGameExternalOptions.gEnemyHeliTimePerHPRepair; + + // are we able to fund this? + if ( gGameExternalOptions.gEnemyHeliMaxHP - heli.hp * gGameExternalOptions.gEnemyHeliCostPerRepair1HP <= gASDResource[ASD_MONEY] ) + { + heli.flagmask |= ENEMYHELI_REPAIRING; + + AddStrategicEvent( EVENT_ENEMY_HELI_REPAIR, GetWorldTotalMin( ) + time, id ); + + return; + } + } + + if ( needrefuel ) + { + // determine how long this will take + INT32 time = (gGameExternalOptions.gEnemyHeliMaxFuel - heli.fuel) * gGameExternalOptions.gEnemyHeliTimePerFuelRefuel; + + // are we able to fund this? + if ( gGameExternalOptions.gEnemyHeliMaxFuel - heli.fuel <= gASDResource[ASD_FUEL] ) + { + heli.flagmask |= ENEMYHELI_REFUELING; + + AddStrategicEvent( EVENT_ENEMY_HELI_REFUEL, GetWorldTotalMin( ) + time, id ); + + return; + } + } + } + else + { + // if we are not at the base and are not ordered to drop or pick up troops, head home + if ( !(heli.flagmask & (ENEMYHELI_ORDER_GOTODESTINATION | ENEMYHELI_ORDER_DROPTROOPS | ENEMYHELI_ORDER_PICKUPTROOPS)) && heli.sector_current != heli.sector_home ) + { + // we don't care about number of SAM contacts here - just get us home! + heli.SetHeliFlightPath( heli.sector_home ); + } + } + } + + // if we still have to move, well, do so + if ( heli.flagmask & (ENEMYHELI_ORDER_GOTODESTINATION) ) + { + AddStrategicEvent( EVENT_ENEMY_HELI_UPDATE, GetWorldTotalMin( ) + gEnemyHeliTravelTimePerSector, id ); + + return; + } + + // if we have to drop troops, do so + if ( heli.flagmask & ENEMYHELI_ORDER_DROPTROOPS && heli.sector_current == heli.sector_destination ) + { + // for now, we park if there is an ongoing battle here + if ( gTacticalStatus.fEnemyInSector || (gTacticalStatus.uiFlags & INCOMBAT) ) + { + AddStrategicEvent( EVENT_ENEMY_HELI_UPDATE, GetWorldTotalMin( ) + gEnemyHeliTravelTimePerSector, id ); + + return; + } + + SectorInfo[heli.sector_current].ubNumElites = min( 255, SectorInfo[heli.sector_current].ubNumElites + heli.troopcount ); + + heli.troopcount = 0; + + heli.flagmask &= ~ENEMYHELI_ORDER_DROPTROOPS; + + // if no militia or mercs are here, enemy takes this sector + if ( PlayerMercsInSector( SECTORX( heli.sector_current ), SECTORY( heli.sector_current ), 0 ) + NumNonPlayerTeamMembersInSector( SECTORX( heli.sector_current ), SECTORY( heli.sector_current ), MILITIA_TEAM ) <= 0 ) + { + SetThisSectorAsEnemyControlled( SECTORX( heli.sector_current ), SECTORY( heli.sector_current ), 0, TRUE ); + } + + CheckCombatInSectorDueToUnusualEnemyArrival( ENEMY_TEAM, SECTORX( heli.sector_current ), SECTORY( heli.sector_current ), 0 ); + + // get new orders + return UpdateEnemyHeli( id ); + } + + // if we have to pick up troops, do so + if ( heli.flagmask & ENEMYHELI_ORDER_PICKUPTROOPS && heli.sector_current == heli.sector_destination ) + { + // for now, not if the player is here + if ( !PlayerMercsInSector( SECTORX( heli.sector_current ), SECTORY( heli.sector_current ), 0 ) ) + { + if ( heli.troopcount < gEnemyHeliMaxTroops ) + { + UINT16 possibletroops = SectorInfo[heli.sector_current].ubNumElites; + + UINT8 picked_up = min( possibletroops, gEnemyHeliMaxTroops - heli.troopcount ); + + heli.troopcount += picked_up; + + SectorInfo[heli.sector_current].ubNumElites -= picked_up; + + heli.flagmask &= ~ENEMYHELI_ORDER_PICKUPTROOPS; + + // get new orders + return UpdateEnemyHeli( id ); + } + } + } + } +} + +void EnemyHeliSAMCheck( INT16 id ) +{ + if ( id >= 0 && id < gEnemyHeliVector.size( ) ) + { + ENEMY_HELI& heli = gEnemyHeliVector[id]; + + // if the heli is already destroyed, nothing to do here + if ( heli.flagmask & ENEMYHELI_DESTROYED ) + return; + + INT8 heli_x = SECTORX( heli.sector_current ); + INT8 heli_y = SECTORY( heli.sector_current ); + + // is the airspace in our current sector player-controlled and operational? + if ( StrategicMap[CALCULATE_STRATEGIC_INDEX( heli_x, heli_y )].usAirType == AIRSPACE_PLAYER_ACTIVE ) + { + UINT8 ubControllingSAM = ubSAMControlledSectors[heli_y][heli_x]; + + StrategicMapElement *pSAMStrategicMap = &(StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX( pSamList[ubControllingSAM - 1] )]); + + if ( pSAMStrategicMap ) + { + UINT16 ubBestSAMOperatorID = NOBODY; + FLOAT samcth = GetBestSAMOperatorCTH_Player( gpSamSectorX[ubControllingSAM - 1], gpSamSectorY[ubControllingSAM - 1], 0, &ubBestSAMOperatorID ); + + // cth is reduced if SAM is damaged, even if it can still operate + samcth = (samcth * pSAMStrategicMap->bSAMCondition) / 100.0f; + + // determine distance from SAM to heli + FLOAT distance = sqrt( abs( heli_x - gpSamSectorX[ubControllingSAM - 1] ) * abs( heli_x - gpSamSectorX[ubControllingSAM - 1] ) + abs( heli_y - gpSamSectorY[ubControllingSAM - 1] ) * abs( heli_y - gpSamSectorY[ubControllingSAM - 1] ) ); + + // distance penalty + samcth = (samcth * (100.0f - 8 * distance)) / 100.0f; + + // if cth is too low, we don't fire + if ( samcth > 0.01 ) + { + CHAR16 pStrSectorName[128]; + GetSectorIDString( heli_x, heli_y, 0, pStrSectorName, FALSE ); + + CHAR16 pStrSectorName_SAM[128]; + GetSectorIDString( gpSamSectorX[ubControllingSAM - 1], gpSamSectorY[ubControllingSAM - 1], 0, pStrSectorName_SAM, FALSE ); + + MapScreenMessage( FONT_MCOLOR_LTRED, MSG_INTERFACE, szEnemyHeliText[8], pStrSectorName_SAM, pStrSectorName ); + + // if we hit, damage the heli + BOOLEAN fHit = FALSE; + BOOLEAN fDestroyed = FALSE; + + if ( Chance( (UINT32)(samcth) ) ) + { + fHit = TRUE; + + UINT8 damage = gGameExternalOptions.gEnemyHeliSAMDamage_Base + Random( gGameExternalOptions.gEnemyHeliSAMDamage_Var ); + + MapScreenMessage( FONT_MCOLOR_LTRED, MSG_INTERFACE, szEnemyHeliText[6], pStrSectorName ); + + heli.hp = max( 0, heli.hp - damage ); + + if ( !heli.hp ) + { + fDestroyed = TRUE; + + heli.flagmask |= ENEMYHELI_SHOTDOWN; + + heli.Destroy( ); + } + } + + // depending on our result, award experience to the operator + if ( ubBestSAMOperatorID != NOBODY ) + { + if ( fHit ) + StatChange( MercPtrs[ubBestSAMOperatorID], EXPERAMT, fDestroyed ? 30 : 10, TRUE ); + + StatChange( MercPtrs[ubBestSAMOperatorID], MECHANAMT, 5, TRUE ); + } + } + } + } + + EnemyHeliMANPADSCheck( id ); + } +} + +void EnemyHeliMANPADSCheck( INT16 id ) +{ + if ( id >= 0 && id < gEnemyHeliVector.size( ) ) + { + ENEMY_HELI& heli = gEnemyHeliVector[id]; + + // if the heli is already destroyed, nothing to do here + if ( heli.flagmask & ENEMYHELI_DESTROYED ) + return; + + // if the player doesn't know about this heli, he can't shoot it down + if ( !(heli.flagmask & ENEMYHELI_KNOWNTOPLAYER) ) + return; + + INT8 heli_x = SECTORX( heli.sector_current ); + INT8 heli_y = SECTORY( heli.sector_current ); + + CHAR16 pStrSectorName[128]; + GetSectorIDString( heli_x, heli_y, 0, pStrSectorName, FALSE ); + + // each merc can fire one missile at the heli (break if heli is shot down) + // this requires an armed SAM in the first place. The mercs have to be in the heli's sector or adjacent to it + // sleeping or travelling mercs are excluded + UINT16 uiCnt = 0; + SOLDIERTYPE* pSoldier = NULL; + + for ( uiCnt = 0, pSoldier = MercPtrs[uiCnt]; uiCnt <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++uiCnt, ++pSoldier ) + { + if ( pSoldier && pSoldier->bActive && pSoldier->stats.bLife >= OKLIFE && !pSoldier->bSectorZ && + (pSoldier->sSectorX == heli_x && pSoldier->sSectorY == heli_y) || + (pSoldier->sSectorX == heli_x + 1 && pSoldier->sSectorY == heli_y) || + (pSoldier->sSectorX == heli_x - 1 && pSoldier->sSectorY == heli_y) || + (pSoldier->sSectorX == heli_x && pSoldier->sSectorY == heli_y + 1) || + (pSoldier->sSectorX == heli_x && pSoldier->sSectorY == heli_y - 1) + ) + { + // look for a SAM in our inventory + OBJECTTYPE* pObj = pSoldier->GetObjectWithFlag( MANPAD ); + + if ( pObj ) + { + // abort if this is a launcher without ammo + if ( Item[pObj->usItem].rocketlauncher && !Item[pObj->usItem].singleshotrocketlauncher ) + { + OBJECTTYPE* pAttachment = FindAttachmentByClass( pObj, IC_GRENADE ); + if ( !pAttachment->exists( ) ) + pAttachment = FindAttachmentByClass( pObj, IC_BOMB ); + + if ( !pAttachment->exists( ) ) + continue; + } + + // determine cth + INT16 samcth = pSoldier->stats.bMarksmanship; + + samcth = (FLOAT)((samcth * (100.0f + pSoldier->GetTraitCTHModifier( pObj->usItem, 2, NO_PROFILE ))) / 100.0f); + + samcth = (samcth * (100.0f + pSoldier->GetBackgroundValue( BG_PERC_SAM_CTH ))) / 100.0f; + + // cth is reduced if SAM is damaged, even if it can still operate + samcth = (samcth * (*pObj)[0]->data.objectStatus) / 100.0f; + + // determine distance from SAM to heli + FLOAT distance = sqrt( abs( heli_x - pSoldier->sSectorX ) * abs( heli_x - pSoldier->sSectorX ) + abs( heli_y - pSoldier->sSectorY ) * abs( heli_y - pSoldier->sSectorY ) ); + + // distance penalty + samcth = (samcth * (100.0f - 20 * distance)) / 100.0f; + + MapScreenMessage( FONT_MCOLOR_LTRED, MSG_INTERFACE, szEnemyHeliText[7], pSoldier->GetName( ), Item[pObj->usItem].szItemName, pStrSectorName ); + + // 'fire' (remove shot) + if ( Item[pObj->usItem].singleshotrocketlauncher ) + { + CreateItem( Item[pObj->usItem].discardedlauncheritem, (*pObj)[0]->data.objectStatus, pObj ); + + DirtyMercPanelInterface( pSoldier, DIRTYLEVEL2 ); + } + else + { + DeductAmmo( pSoldier, pObj ); + } + + // if we hit, damage the heli + BOOLEAN fHit = FALSE; + BOOLEAN fDestroyed = FALSE; + + if ( Chance( (UINT32)(samcth) ) ) + { + fHit = TRUE; + + UINT8 damage = gGameExternalOptions.gEnemyHeliMANPADSDamage_Base + Random( gGameExternalOptions.gEnemyHeliMANPADSDamage_Var ); + + MapScreenMessage( FONT_MCOLOR_LTRED, MSG_INTERFACE, szEnemyHeliText[6], pStrSectorName ); + + heli.hp = max( 0, heli.hp - damage ); + + if ( !heli.hp ) + { + fDestroyed = TRUE; + + heli.flagmask |= ENEMYHELI_SHOTDOWN; + + heli.Destroy( ); + } + } + + // depending on our result, award experience to the operator + if ( fHit ) + StatChange( pSoldier, EXPERAMT, fDestroyed ? 30 : 10, TRUE ); + + StatChange( pSoldier, MARKAMT, 5, TRUE ); + } + } + } + } +} + +void EnemyHeliCheckPlayerKnowledge( INT16 id ) +{ + if ( id >= 0 && id < gEnemyHeliVector.size( ) ) + { + ENEMY_HELI& heli = gEnemyHeliVector[id]; + + INT8 current_x = SECTORX( heli.sector_current ); + INT8 current_y = SECTORY( heli.sector_current ); + + // if there are mercs in this sector, they spot the heli and let us know + if ( PlayerMercsInSector( current_x, current_y, 0 ) ) + { + // if we previously did not know of the heli, our mercs will alert us + if ( !(heli.flagmask & ENEMYHELI_KNOWNTOPLAYER) ) + { + SayQuoteFromAnyBodyInThisSector( current_x, current_y, 0, QUOTE_AIR_RAID ); + } + + heli.flagmask |= ENEMYHELI_KNOWNTOPLAYER; + + return; + } + + // scouts our militia might be able to detect a heli from nearby sectors + if ( CanSomeoneNearbyScoutThisSector( current_x, current_y, TRUE ) ) // scouting trait check + { + if ( gSkillTraitValues.fSCCanDetectEnemyPresenseAround ) + { + heli.flagmask |= ENEMYHELI_KNOWNTOPLAYER; + + return; + } + } + + // is the airspace in our current sector player-controlled and operational? + if ( StrategicMap[CALCULATE_STRATEGIC_INDEX( current_x, current_y )].usAirType == AIRSPACE_PLAYER_ACTIVE ) + { + UINT8 ubControllingSAM = ubSAMControlledSectors[current_y][current_x]; + + // if mercs man the SAM, they will let us know of the new SAM + if ( PlayerMercsInSector( gpSamSectorX[ubControllingSAM - 1], gpSamSectorY[ubControllingSAM - 1], 0 ) ) + { + // if we previously did not know of the heli, our mercs will alert us + if ( !(heli.flagmask & ENEMYHELI_KNOWNTOPLAYER) ) + { + SayQuoteFromAnyBodyInThisSector( gpSamSectorX[ubControllingSAM - 1], gpSamSectorY[ubControllingSAM - 1], 0, QUOTE_AIR_RAID ); + } + } + + heli.flagmask |= ENEMYHELI_KNOWNTOPLAYER; + + return; + } + + // if we haven't exited this function already, nobody notices the heli - delete knowledge + heli.flagmask &= ~ENEMYHELI_KNOWNTOPLAYER; + } +} + +void RepairSamSite( UINT8 aSector ) +{ + if ( IsThisSectorASAMSector( SECTORX( aSector ), SECTORY( aSector ), 0 ) ) + { + StrategicMap[aSector].bSAMCondition = 100; + StrategicMap[aSector].usFlags &= ~SAMSITE_REPAIR_ORDERED; + + UpdateSAMDoneRepair( SECTORX( aSector ), SECTORY( aSector ), 0 ); + } +} + +BOOLEAN IsSectorAirSpacePlayerControlled( UINT8 aSector ) +{ + return (StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX( aSector )].usAirType == AIRSPACE_PLAYER_ACTIVE); +} + +UINT8 NumPlayerAirSpaceOnHeliPath( UINT8 aStart, UINT8 aEnd ) +{ + UINT8 samcontacts = 0; + + UINT8 tmpsect = aStart; + + while ( tmpsect != aEnd ) + { + tmpsect = GetNextEnemyHeliSector( tmpsect, aEnd ); + + if ( IsSectorAirSpacePlayerControlled( tmpsect ) ) + ++samcontacts; + } + + return samcontacts; +} + +// get the next sector in an enemy heli flight path. +UINT8 GetNextEnemyHeliSector( UINT8 aStart, UINT8 aDest ) +{ + INT8 start_x = SECTORX( aStart ); + INT8 start_y = SECTORY( aStart ); + + INT8 dest_x = SECTORX( aDest ); + INT8 dest_y = SECTORY( aDest ); + + if ( abs( start_x - dest_x ) > abs( start_y - dest_y ) ) + { + if ( start_x > dest_x ) + return SECTOR( start_x - 1, start_y ); + else + return SECTOR( start_x + 1, start_y ); + } + else + { + if ( start_y > dest_y ) + return SECTOR( start_x, start_y - 1 ); + else + return SECTOR( start_x, start_y + 1 ); + } + + return aStart; +} + + +void HandleEnemyHelicopterOnGroundGraphic( ) +{ + // no worries if underground + if ( gbWorldSectorZ ) + return; + + UINT8 currentsector = SECTOR( gWorldSectorX, gWorldSectorY ); + + // only relevant if this is the enemies' heli hub + if ( currentsector != gGameExternalOptions.usEnemyHeliBaseSector ) + return; + + INT16 id = -1; + + std::vector::iterator itend = gEnemyHeliVector.end( ); + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != itend; ++it ) + { + ++id; + + // we can only provide graphics for ENEMYHELI_MAX helis + if ( id >= ENEMYHELI_MAX ) + break; + + AddEnemyHelicopterToMaps( ((*it).sector_current == currentsector), ((*it).flagmask & ENEMYHELI_DESTROYED), gGameExternalOptions.sEnemyHeliBaseParkGridno[id], gGameExternalOptions.sEnemyHeliBaseParkTileIndex ); + } + + // Invalidate rendering (if we have any helis at all) + if ( id ) + InvalidateWorldRedundency( ); +} + +void UpdateAndDamageEnemyHeliIfFound( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, INT32 sGridNo, UINT8 ubDamage, BOOLEAN aDestroyed ) +{ + // no worries if underground + if ( gbWorldSectorZ ) + return; + + UINT8 currentsector = SECTOR( gWorldSectorX, gWorldSectorY ); + + // only relevant if this is the enemies' heli hub + if ( currentsector != gGameExternalOptions.usEnemyHeliBaseSector ) + return; + + // helicopter tiles are multi-tiled. Search for the base gridno + STRUCTURE *pCurrent, *pBaseStructure; + INT32 sBaseGridNo = NOWHERE; + + pCurrent = gpWorldLevelData[sGridNo].pStructureHead; + + while ( pCurrent != NULL ) + { + pBaseStructure = FindBaseStructure( pCurrent ); + + if ( pBaseStructure && pBaseStructure->ubHitPoints ) + { + sBaseGridNo = pBaseStructure->sGridNo; + + break; + } + + pCurrent = pCurrent->pNext; + } + + if ( sBaseGridNo == NOWHERE ) + return; + + INT16 id = -1; + + std::vector::iterator itend = gEnemyHeliVector.end( ); + for ( std::vector::iterator it = gEnemyHeliVector.begin( ); it != itend; ++it ) + { + ++id; + + // we can only provide graphics for ENEMYHELI_MAX helis + if ( id >= ENEMYHELI_MAX ) + continue; + + // if the heli is already destroyed, nothing to do here + if ( (*it).flagmask & ENEMYHELI_DESTROYED ) + continue; + + if ( (*it).sector_current == currentsector ) + { + if ( gGameExternalOptions.sEnemyHeliBaseParkGridno[id] == sBaseGridNo ) + { + if ( aDestroyed ) + { + (*it).Destroy( ); + } + // if the structure is not destroyed, the heli stays + else + { + (*it).hp = max( 1, (*it).hp - ubDamage ); + } + } + } + } +} + +//////////////////////////////////////////// enemy tanks ///////////////////////////////////////////////////////// +// TODO: ini this +INT32 gASDResource_Cost_Fuel_Tank = 100; // how many units of fuel a tank needs (used upon creation) +INT32 gASDResource_Cost_Fuel_Jeep = 20; + +UINT32 ASDResourceCostFuel( UINT8 aType ) +{ + switch ( aType ) + { + case ASD_TANK: + return gASDResource_Cost_Fuel_Tank; + break; + + case ASD_JEEP: + return gASDResource_Cost_Fuel_Jeep; + break; + } + + return 0; +} + +// if ASD has tanks, it can allow the queen to upgrade soldiers to tanks +BOOLEAN ASDSoldierUpgradeToTank( ) +{ + // TODO: ini val for feature + if ( gGameExternalOptions.fASDActive && 0 ) + { + if ( gASD_Flags & ASDFACT_TANK_UNLOCKED ) + { + if ( gASDResource[ASD_TANK] ) + { + gASDResource[ASD_TANK] = max( 0, gASDResource[ASD_TANK] - 1 ); + return TRUE; + } + } + } + else + { + gASDResource[ASD_TANK] = max( 0, gASDResource[ASD_TANK] - 1 ); + return TRUE; + } + + return FALSE; +} diff --git a/Strategic/ASD.h b/Strategic/ASD.h new file mode 100644 index 00000000..83002ae3 --- /dev/null +++ b/Strategic/ASD.h @@ -0,0 +1,120 @@ +#ifndef __ASD_H +#define __ASD_H + +/** +* @file +* @author Flugente (bears-pit.com) +*/ + +#include "Types.h" + +/** Flugente: Arulco special division decision code + * + * As I do not want to alter the current strategic AI (fragile and obscure as it is), + * I am leaving the control over a number of new toys to a second 'AI' - the 'Arulco Special Division'. + * This should control if, how and when new features are used by the AI. + * + * It has its own 'budget', which allows it to purchase new actions. This serves to limit the amount of the AIs actions. + * It also gives the player a way to compete with the AI - lower the AI budget, and the AI can do less harm. + * For example, part of the AI budget might come from mine income, which the player can take away. + * + * If possible, the AI should have to buy and maintain its assets. + * This way the player could harm the AI by specifically targetting those assets if he chooses so. + * Ideally this gives the player another immersive way to do sth. in the game. + * For example, enemy helicopters have to be bought, fuelled and repaired. + * In order to stop enemy heli raids, the player could + * - shoot them down + * - steal enemy fuel deliveries + * - sabotage enemy helis in their airfield + * + * There is a cyclic update of this AI. Each time the AI decides whether it wants (and can afford) new assets. + * These assets then have to be ordered and can only be used once arrived. + * The AI also decides how to use these assets. + */ + +void InitASD( ); +BOOLEAN SaveASDData( HWFILE hwFile ); +BOOLEAN LoadASDData( HWFILE hwFile ); + +// when called, evaluate the current situation, and perform actions to harm the player if necessary +void UpdateASD( ); + +// decide whether to purchase something +void ASDDecideOnPurchases(); + +// decide in what to do with helicopters +void ASDDecideHeliOperations(); + +// various flags for the AI to remember +#define ASDFACT_HELI_UNLOCKED 0x00000001 // AI is allowed to purchase helicopters +#define ASDFACT_JEEP_UNLOCKED 0x00000002 // AI is allowed to purchase jeeps +#define ASDFACT_TANK_UNLOCKED 0x00000004 // AI is allowed to purchase tanks + +void SetASDFlag( UINT32 aFlag ); + +UINT32 ASDResourceDeliveryTime( UINT8 aType ); +UINT32 ASDResourceCostMoney( UINT8 aType ); + +// add resources to the AIs resource pool +void AddStrategicAIResources( UINT8 aType, INT32 aAmount ); +void ASDReceiveOrderedStrategicAIResources( UINT8 aType, INT32 aAmount ); + +// enemy helis +class ENEMY_HELI +{ +public: + // determine best heli flight path (minimising contact to hostile SAM sectors) and return number of possible SAM contacts + UINT8 SetHeliFlightPath( UINT8 aDest ); + + void Destroy( ); + +public: + UINT32 flagmask; // flags: destroyed, refueling... + UINT8 sector_current; // we are here + UINT8 sector_destination; // we want to fly here + UINT8 sector_waypoint; // a sector we travel to between our starting point and our destiantion. Choosing this gives us a way to plot a path minimising SAM-exposure + UINT8 sector_home; // this is where our base is + UINT8 troopcount; // number of troops we carry + UINT8 hp; + UINT8 fuel; +}; + +extern std::vector gEnemyHeliVector; + +void EnemyHeliInit( ); + +void AddNewHeli( ); + +// all enemy helis carrying troops in this sector drop these into combat, and get the order to head home +void EnemyHeliTroopDrop( UINT8 aSector ); + +std::set GetEnemyHeliSectors( BOOLEAN afKnownToPlayer ); + +void UpdateEnemyHeliRepair( INT16 id ); +void UpdateEnemyHeliRefuel( INT16 id ); +void UpdateEnemyHeli( INT16 id ); +void EnemyHeliSAMCheck( INT16 id ); +void EnemyHeliMANPADSCheck( INT16 id ); +void EnemyHeliCheckPlayerKnowledge( INT16 id ); + +void RepairSamSite( UINT8 aSector ); + +BOOLEAN IsSectorAirSpacePlayerControlled( UINT8 aSector ); + +UINT8 NumPlayerAirSpaceOnHeliPath( UINT8 aStart, UINT8 aEnd ); + +// get the next sector in an enemy heli flight path. +UINT8 GetNextEnemyHeliSector( UINT8 aStart, UINT8 aDest ); + +void HandleEnemyHelicopterOnGroundGraphic( ); + +void UpdateAndDamageEnemyHeliIfFound( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, INT32 sGridNo, UINT8 ubDamage, BOOLEAN aDestroyed = FALSE ); + + +// enemy tanks (how and when they are used) +UINT32 ASDResourceCostFuel( UINT8 aType ); + +// if ASD is used, any tanks the queen uses in mobile attacks come from its pool, and we have to account for that +BOOLEAN ASDSoldierUpgradeToTank( ); + +#endif //__ASD_H diff --git a/Strategic/Assignments.cpp b/Strategic/Assignments.cpp index 99bf5569..16ea468d 100644 --- a/Strategic/Assignments.cpp +++ b/Strategic/Assignments.cpp @@ -3209,6 +3209,44 @@ UINT32 CalculateSnitchPrisonGuardValue(SOLDIERTYPE *pSoldier, UINT16 *pusMaxPts return( usValue ); } +// Flugente: Determine the best cth with SAMs in a sector, and which merc has that cth if present +FLOAT GetBestSAMOperatorCTH_Player( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, UINT16 *pubID ) +{ + // if nobody is here, nobody can fire + FLOAT bestsamcth = 0.0f; + *pubID = NOBODY; + + // militia can at least operate the thing, but don't count on them hitting anything... + if ( NumNonPlayerTeamMembersInSector( sSectorX, sSectorY, MILITIA_TEAM ) ) + bestsamcth = 40.0f; + + // loop over all mercs present. Best cth wins + UINT16 uiCnt = 0; + SOLDIERTYPE* pSoldier = NULL; + + for ( uiCnt = 0, pSoldier = MercPtrs[uiCnt]; uiCnt <= gTacticalStatus.Team[gbPlayerNum].bLastID; ++uiCnt, ++pSoldier ) + { + if ( pSoldier && pSoldier->bActive && pSoldier->stats.bLife >= OKLIFE && (pSoldier->sSectorX == sSectorX) && (pSoldier->sSectorY == sSectorY) && (pSoldier->bSectorZ == sSectorZ) ) + { + INT16 personal_bestsamcth = 70.0f + + 15 * NUM_SKILL_TRAITS( pSoldier, HEAVY_WEAPONS_NT ) + + 10 * NUM_SKILL_TRAITS( pSoldier, TECHNICIAN_NT ) + + 2 * NUM_SKILL_TRAITS( pSoldier, DEMOLITIONS_NT ) + + 5 * NUM_SKILL_TRAITS( pSoldier, RADIO_OPERATOR_NT ); + + personal_bestsamcth = (personal_bestsamcth * (100.0f + pSoldier->GetBackgroundValue( BG_PERC_SAM_CTH ))) / 100.0f; + + if ( personal_bestsamcth > bestsamcth ) + { + bestsamcth = personal_bestsamcth; + *pubID = uiCnt; + } + } + } + + return bestsamcth; +} + // anv: handle prisoners exposing snitch as a snitch BOOL HandleSnitchExposition(SOLDIERTYPE *pSoldier) { diff --git a/Strategic/Assignments.h b/Strategic/Assignments.h index 869a6808..c4353e79 100644 --- a/Strategic/Assignments.h +++ b/Strategic/Assignments.h @@ -260,6 +260,9 @@ UINT32 CalculatePrisonGuardValue(SOLDIERTYPE *pSoldier, UINT16 *pusMaxPts ); UINT32 CalculateSnitchInterrogationValue(SOLDIERTYPE *pSoldier, UINT16 *pusMaxPts ); +// Flugente: Determine the best cth with SAMs in a sector, and which merc has that cth if present +FLOAT GetBestSAMOperatorCTH_Player( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, UINT16 *pubID ); + // Flugente: determine max items we can move, and the sector distance // get bonus tarining pts due to an instructor for this student diff --git a/Strategic/Campaign Types.h b/Strategic/Campaign Types.h index 24d58779..03f9bec1 100644 --- a/Strategic/Campaign Types.h +++ b/Strategic/Campaign Types.h @@ -441,6 +441,7 @@ typedef enum // -------- added by Flugente: sector info flags -------- // easier than adding 32 differently named variables. DO NOT CHANGE THEM, UNLESS YOU KNOW WHAT YOU ARE DOING!!! #define SECTORINFO_VOLUNTEERS_RECENTLY_RECRUITED 0x01 //1 // we recruited volunteers here. Until this flag is removed, newly created civilians wont be potential volunteers anymore +#define SECTORINFO_ENEMYHELI_SHOTDOWN 0x02 //2 // an enemy helicopter was shot down here. The first time after that we enter this sector, there is a chance to find a downed pilot here typedef struct SECTORINFO { diff --git a/Strategic/Facilities.cpp b/Strategic/Facilities.cpp index 2fba5653..2a70acbb 100644 --- a/Strategic/Facilities.cpp +++ b/Strategic/Facilities.cpp @@ -717,7 +717,7 @@ void HandleManualPaymentFacilityDebt( void ) // HEADROCK HAM 3.6: Apply income bonuses from Facility Work to a specific mine. INT32 MineIncomeModifierFromFacility( UINT8 ubMine ) { - Assert (ubMine > 0 && ubMine < MAX_NUMBER_OF_MINES); + Assert (ubMine >= 0 && ubMine < MAX_NUMBER_OF_MINES); SOLDIERTYPE *pSoldier; INT32 iModifier = 0; diff --git a/Strategic/Game Event Hook.cpp b/Strategic/Game Event Hook.cpp index ce199e1c..85e251da 100644 --- a/Strategic/Game Event Hook.cpp +++ b/Strategic/Game Event Hook.cpp @@ -47,6 +47,8 @@ #include "MilitiaSquads.h" #include "PMC.h" // added by Flugente #include "finances.h" // added by Flugente + #include "ASD.h" // added by Flugente + #include "Player Command.h" // added by Flugente #endif #include "connect.h" @@ -573,7 +575,47 @@ BOOLEAN ExecuteStrategicEvent( STRATEGICEVENT *pEvent ) EndQuest( QUEST_KINGPIN_ANGEL_MARIA, gWorldSectorX, gWorldSectorY ); break; - #endif +#endif + + case EVENT_ASD_UPDATE: + UpdateASD(); + break; + + case EVENT_ASD_PURCHASE_FUEL: + if ( IsSectorEnemyControlled( SECTORX( gGameExternalOptions.usASDSupplyArrivalSector ), SECTORY( gGameExternalOptions.usASDSupplyArrivalSector ), 0 ) ) + ASDReceiveOrderedStrategicAIResources( ASD_FUEL, pEvent->uiParam ); + break; + + case EVENT_ASD_PURCHASE_JEEP: + if ( IsSectorEnemyControlled( SECTORX( gGameExternalOptions.usASDSupplyArrivalSector ), SECTORY( gGameExternalOptions.usASDSupplyArrivalSector ), 0 ) ) + ASDReceiveOrderedStrategicAIResources( ASD_JEEP, pEvent->uiParam ); + break; + + case EVENT_ASD_PURCHASE_TANK: + if ( IsSectorEnemyControlled( SECTORX( gGameExternalOptions.usASDSupplyArrivalSector ), SECTORY( gGameExternalOptions.usASDSupplyArrivalSector ), 0 ) ) + ASDReceiveOrderedStrategicAIResources( ASD_TANK, pEvent->uiParam ); + break; + + case EVENT_ASD_PURCHASE_HELI: + if ( IsSectorEnemyControlled( SECTORX( gGameExternalOptions.usEnemyHeliBaseSector ), SECTORY( gGameExternalOptions.usEnemyHeliBaseSector ), 0 ) ) + ASDReceiveOrderedStrategicAIResources( ASD_HELI, pEvent->uiParam ); + break; + + case EVENT_ENEMY_HELI_UPDATE: + UpdateEnemyHeli( pEvent->uiParam ); + break; + + case EVENT_ENEMY_HELI_REPAIR: + UpdateEnemyHeliRepair( pEvent->uiParam ); + break; + + case EVENT_ENEMY_HELI_REFUEL: + UpdateEnemyHeliRefuel( pEvent->uiParam ); + break; + + case EVENT_SAMSITE_REPAIRED: + RepairSamSite( pEvent->uiParam ); + break; } gfPreventDeletionOfAnyEvent = fOrigPreventFlag; return TRUE; diff --git a/Strategic/Game Event Hook.h b/Strategic/Game Event Hook.h index 9ef0317f..94ff2023 100644 --- a/Strategic/Game Event Hook.h +++ b/Strategic/Game Event Hook.h @@ -120,6 +120,18 @@ enum EVENT_KINGPIN_BOUNTY_END_KILLEDTHEM, EVENT_KINGPIN_BOUNTY_END_TIME_PASSED, + EVENT_ASD_UPDATE, // Flugente: the queen's special division decides it's next action + EVENT_ASD_PURCHASE_FUEL, + EVENT_ASD_PURCHASE_JEEP, + EVENT_ASD_PURCHASE_TANK, + EVENT_ASD_PURCHASE_HELI, + + EVENT_ENEMY_HELI_UPDATE, // Flugente: move enemy helicopter to another sector + EVENT_ENEMY_HELI_REPAIR, + EVENT_ENEMY_HELI_REFUEL, + + EVENT_SAMSITE_REPAIRED, // Flugente: have a SAM site be fully repaired + NUMBER_OF_EVENT_TYPES_PLUS_ONE, NUMBER_OF_EVENT_TYPES = NUMBER_OF_EVENT_TYPES_PLUS_ONE - 1 }; diff --git a/Strategic/Game Events.cpp b/Strategic/Game Events.cpp index 2340310a..640e17a6 100644 --- a/Strategic/Game Events.cpp +++ b/Strategic/Game Events.cpp @@ -115,6 +115,15 @@ CHAR16 gEventName[NUMBER_OF_EVENT_TYPES_PLUS_ONE][40]={ L"KingpinBounty1", L"KingpinBounty2", L"KingpinBounty3", + L"ASDUpdate", + L"ASDPurchaseFuel", + L"ASDPurchaseJeep", + L"ASDPurchaseTank", + L"ASDPurchaseHeli", + L"EnemyHeliUpdate", + L"EnemyHeliRepair", + L"EnemyHeliRefuel", + L"SAMsiteRepaired", }; #endif diff --git a/Strategic/Game Init.cpp b/Strategic/Game Init.cpp index b7c96740..02f4c7b9 100644 --- a/Strategic/Game Init.cpp +++ b/Strategic/Game Init.cpp @@ -61,6 +61,7 @@ #include "Map Screen Interface Map Inventory.h"//dnl ch51 081009 #include "CampaignStats.h" // added by Flugente #include "PMC.h" // added by Flugente + #include "ASD.h" // added by Flugente #endif #include "Vehicles.h" @@ -499,13 +500,16 @@ void InitStrategicLayer( void ) // re-set up leave list arrays for dismissed mercs InitLeaveList( ); - // Flugente: se up VIP locations - void InitVIPSectors( ); + // Flugente: set up VIP locations + InitVIPSectors(); + // Flugente: init special AI + InitASD(); - #ifdef JA2UB +#ifdef JA2UB LuaInitStrategicLayer(0); //JA25 UB InitStrategicLayer.lua - #endif +#endif + // reset time compression mode to X0 (this will also pause it) SetGameTimeCompressionLevel( TIME_COMPRESS_X0 ); diff --git a/Strategic/Map Screen Helicopter.cpp b/Strategic/Map Screen Helicopter.cpp index f8f5e5c9..676b0ae6 100644 --- a/Strategic/Map Screen Helicopter.cpp +++ b/Strategic/Map Screen Helicopter.cpp @@ -43,6 +43,7 @@ #include "Debug Control.h" #include "expat.h" #include "merc entering.h" // added by Flugente + #include "ASD.h" // added by Flugente #endif #include "Vehicles.h" @@ -106,6 +107,7 @@ INT32 iHelicopterVehicleId = -1; // helicopter icon UINT32 guiHelicopterIcon; +UINT32 guiEnemyHelicopterIcon; // total distance travelled INT32 iTotalHeliDistanceSinceRefuel = 0; @@ -1292,6 +1294,9 @@ void LandHelicopter( void ) HandleKillChopperMeanwhileScene(); #endif } + + // Flugente: once the AI 'learns' of the player using helis, it wants some for itself + SetASDFlag( ASDFACT_HELI_UNLOCKED ); } @@ -2670,19 +2675,19 @@ void AddHelicopterToMaps( BOOLEAN fAdd, UINT8 ubSite ) InvalidateWorldRedundency(); SetRenderFlags( RENDER_FLAG_FULL ); - // ATE: If any mercs here, bump them off! + // ATE: If any mercs here, bump them off! ConvertGridNoToXY( iGridNo, &sCentreGridX, &sCentreGridY ); - for( sGridY = sCentreGridY - 5; sGridY < sCentreGridY + 5; sGridY++ ) - { - for( sGridX = sCentreGridX - 5; sGridX < sCentreGridX + 5; sGridX++ ) + for( sGridY = sCentreGridY - 5; sGridY < sCentreGridY + 5; sGridY++ ) { - iGridNo = MAPROWCOLTOPOS( sGridY, sGridX ); + for( sGridX = sCentreGridX - 5; sGridX < sCentreGridX + 5; sGridX++ ) + { + iGridNo = MAPROWCOLTOPOS( sGridY, sGridX ); - BumpAnyExistingMerc( iGridNo ); + BumpAnyExistingMerc( iGridNo ); + } } } - } else { // remove from the world @@ -2695,9 +2700,62 @@ void AddHelicopterToMaps( BOOLEAN fAdd, UINT8 ubSite ) InvalidateWorldRedundency(); SetRenderFlags( RENDER_FLAG_FULL ); + } +} +void AddEnemyHelicopterToMaps( BOOLEAN fAdd, BOOLEAN fDestroyed, INT32 aGridno, INT32 aTileIndex ) +{ + // for safety, remove the old one first + { + // remove from the world + RemoveStruct( aGridno, (UINT16)aTileIndex ); + RemoveStruct( aGridno, (UINT16)(aTileIndex + 1) ); + RemoveStruct( (aGridno - WORLD_COLS * 5), (UINT16)(aTileIndex + 2) ); + RemoveStruct( aGridno, (UINT16)(aTileIndex + 3) ); + RemoveStruct( aGridno, (UINT16)(aTileIndex + 4) ); + RemoveStruct( (aGridno - WORLD_COLS * 5), (UINT16)(aTileIndex + 5) ); + + // for safety reasons, both versions are to be removed + RemoveStruct( aGridno, (UINT16)aTileIndex + 6 ); + RemoveStruct( aGridno, (UINT16)(aTileIndex + 6 + 1) ); + RemoveStruct( (aGridno - WORLD_COLS * 5), (UINT16)(aTileIndex + 6 + 2) ); + RemoveStruct( aGridno, (UINT16)(aTileIndex + 6 + 3) ); + RemoveStruct( aGridno, (UINT16)(aTileIndex + 6 + 4) ); + RemoveStruct( (aGridno - WORLD_COLS * 5), (UINT16)(aTileIndex + 6 + 5) ); + + InvalidateWorldRedundency( ); + SetRenderFlags( RENDER_FLAG_FULL ); } + if ( fAdd ) + { + INT16 sCentreGridX, sCentreGridY; + + INT16 offset = fDestroyed ? 6 : 0; + + AddHeliPiece( aGridno, (UINT16)aTileIndex + offset ); + AddHeliPiece( aGridno, (UINT16)(aTileIndex + offset + 1) ); + AddHeliPiece( (aGridno - WORLD_COLS * 5), (UINT16)(aTileIndex + offset + 2) ); + AddHeliPiece( aGridno, (UINT16)(aTileIndex + offset + 3) ); + AddHeliPiece( aGridno, (UINT16)(aTileIndex + offset + 4) ); + AddHeliPiece( (aGridno - WORLD_COLS * 5), (UINT16)(aTileIndex + offset + 5) ); + + InvalidateWorldRedundency( ); + SetRenderFlags( RENDER_FLAG_FULL ); + + // ATE: If any mercs here, bump them off! + ConvertGridNoToXY( aGridno, &sCentreGridX, &sCentreGridY ); + + for ( INT16 sGridY = sCentreGridY - 5; sGridY < sCentreGridY + 5; sGridY++ ) + { + for ( INT16 sGridX = sCentreGridX - 5; sGridX < sCentreGridX + 5; sGridX++ ) + { + aGridno = MAPROWCOLTOPOS( sGridY, sGridX ); + + BumpAnyExistingMerc( aGridno ); + } + } + } } diff --git a/Strategic/Map Screen Helicopter.h b/Strategic/Map Screen Helicopter.h index 1395bb64..e8256701 100644 --- a/Strategic/Map Screen Helicopter.h +++ b/Strategic/Map Screen Helicopter.h @@ -99,6 +99,7 @@ extern BOOLEAN fHoveringHelicopter; // helicopter icon extern UINT32 guiHelicopterIcon; +extern UINT32 guiEnemyHelicopterIcon; // helicopter destroyed extern BOOLEAN fHelicopterDestroyed; @@ -293,6 +294,8 @@ BOOLEAN CanHelicopterTakeOff( void ); void InitializeHelicopter( void ); +void AddEnemyHelicopterToMaps( BOOLEAN fAdd, BOOLEAN fDestroyed, INT32 aGridno, INT32 aTileIndex ); + BOOLEAN IsSkyriderIsFlyingInSector( INT16 sSectorX, INT16 sSectorY ); BOOLEAN IsGroupTheHelicopterGroup( GROUP *pGroup ); diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index 04cf2dca..fe4ac50f 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -44,6 +44,7 @@ #include "Map Information.h" #include "Air Raid.h" #include "Auto Resolve.h" + #include "ASD.h" // added by Flugente #endif #include "Quests.h" @@ -4248,6 +4249,77 @@ void DisplayPositionOfHelicopter( void ) } } +void DisplayPositionOfEnemyHelicopter() +{ + static INT16 sOldMapX = 0, sOldMapY = 0; + // INT16 sX =0, sY = 0; + FLOAT flRatio = 0.0; + UINT32 x, y; + UINT16 minX, minY, maxX, maxY; + HVOBJECT hHandle; + INT32 iNumberOfPeopleInHelicopter = 0; + CHAR16 sString[4]; + + INT32 MAP_MVT_ICON_FONT = TINYFONT1; + + AssertMsg( (sOldMapX >= 0) && (sOldMapX < SCREEN_WIDTH), String( "DisplayPositionOfHelicopter: Invalid sOldMapX = %d", sOldMapX ) ); + AssertMsg( (sOldMapY >= 0) && (sOldMapY < SCREEN_HEIGHT), String( "DisplayPositionOfHelicopter: Invalid sOldMapY = %d", sOldMapY ) ); + + // TODO: different icon + + // HEADROCK HAM 5: Now has to be done here to get the size of the helicopter icon. + GetVideoObject( &hHandle, guiEnemyHelicopterIcon ); + + UINT16 usIconWidth = hHandle->pETRLEObject[0].usWidth; + UINT16 usIconHeight = hHandle->pETRLEObject[0].usHeight; + UINT16 usIconOffsetX = hHandle->pETRLEObject[0].sOffsetX; + UINT16 usIconOffsetY = hHandle->pETRLEObject[0].sOffsetY; + + // restore background on map where it is + if ( sOldMapX != 0 ) + { + RestoreExternBackgroundRect( sOldMapX + usIconOffsetX, sOldMapY + usIconOffsetY, usIconWidth, usIconHeight ); + sOldMapX = 0; + } + + std::set helisectorset = GetEnemyHeliSectors( TRUE ); // if set to FALSE, display helicopters regardless of player knowledge + + for ( std::set::iterator it = helisectorset.begin( ); it != helisectorset.end( ); ++it) + { + UINT8 sector = (*it); + UINT8 sector_x = SECTORX( sector ); + UINT8 sector_y = SECTORY( sector ); + + // grab min and max locations to interpolate sub sector position + minX = MAP_VIEW_START_X + MAP_GRID_X * (sector_x); + maxX = MAP_VIEW_START_X + MAP_GRID_X * (sector_x); + minY = MAP_VIEW_START_Y + MAP_GRID_Y * (sector_y); + maxY = MAP_VIEW_START_Y + MAP_GRID_Y * (sector_y); + + AssertMsg( (minX >= 0) && (minX < SCREEN_WIDTH), String( "DisplayPositionOfHelicopter: Invalid minX = %d", minX ) ); + AssertMsg( (maxX >= 0) && (maxX < SCREEN_WIDTH), String( "DisplayPositionOfHelicopter: Invalid maxX = %d", maxX ) ); + AssertMsg( (minY >= 0) && (minY < SCREEN_WIDTH), String( "DisplayPositionOfHelicopter: Invalid minY = %d", minY ) ); + AssertMsg( (maxY >= 0) && (maxY < SCREEN_WIDTH), String( "DisplayPositionOfHelicopter: Invalid maxY = %d", maxY ) ); + + // IMPORTANT: Since min can easily be larger than max, we gotta cast to as signed value + x = (UINT32)(minX + flRatio * ((INT16)maxX - (INT16)minX)); + y = (UINT32)(minY + flRatio * ((INT16)maxY - (INT16)minY)); + + // clip blits to mapscreen region + ClipBlitsToMapViewRegion( ); + + BltVideoObject( FRAME_BUFFER, hHandle, HELI_ICON, x, y, VO_BLT_SRCTRANSPARENCY, NULL ); + + InvalidateRegion( x, y, x + usIconWidth, y + usIconHeight ); + + RestoreClipRegionToFullScreen( ); + + // now store the old stuff + sOldMapX = (INT16)x; + sOldMapY = (INT16)y; + } +} + void DisplayDestinationOfHelicopter( void ) { diff --git a/Strategic/Map Screen Interface Map.h b/Strategic/Map Screen Interface Map.h index be7ac0ef..e4a38665 100644 --- a/Strategic/Map Screen Interface Map.h +++ b/Strategic/Map Screen Interface Map.h @@ -169,8 +169,9 @@ INT16 GetLastSectorOfMilitiaPath( void ); // display info about helicopter path void DisplayDistancesForHelicopter( void ); -// display where hei is +// display where heli is void DisplayPositionOfHelicopter( void ); +void DisplayPositionOfEnemyHelicopter(); // check for click BOOLEAN CheckForClickOverHelicopterIcon( INT16 sX, INT16 sY ); diff --git a/Strategic/Map Screen Interface TownMine Info.cpp b/Strategic/Map Screen Interface TownMine Info.cpp index 49ee7fef..be06b60e 100644 --- a/Strategic/Map Screen Interface TownMine Info.cpp +++ b/Strategic/Map Screen Interface TownMine Info.cpp @@ -29,6 +29,8 @@ #include "GameSettings.h" #include "debug.h" #include "Overhead.h" // added by Flugente + #include "Game Clock.h" // added by Flugente + #include "Game Event Hook.h" // added by Flugente #endif #include "Strategic Mines.h" @@ -49,8 +51,10 @@ INT8 bCurrentTownMineSectorY = 0; INT8 bCurrentTownMineSectorZ = 0; // inventory button -UINT32 guiMapButtonInventoryImage[2]; -UINT32 guiMapButtonInventory[2]; +UINT32 guiMapButtonInventoryImage[3]; +UINT32 guiMapButtonInventory[3]; + +BOOLEAN guiSAMButtonDefined = FALSE; UINT16 sTotalButtonWidth = 0; @@ -91,12 +95,13 @@ void AddInventoryButtonForMapPopUpBox( void ); // now remove the above button void RemoveInventoryButtonForMapPopUpBox( void ); -// callback to turn on sector invneotry list +// callback to turn on sector inventory list void MapTownMineInventoryButtonCallBack( GUI_BUTTON *btn, INT32 reason ); void MapTownMineExitButtonCallBack( GUI_BUTTON *btn, INT32 reason ); +void MapTownSAMRepairButtonCallBack( GUI_BUTTON *btn, INT32 reason ); void MinWidthOfTownMineInfoBox( void ); - +extern INT32 GetCurrentBalance( void ); void DisplayTownInfo( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) { @@ -207,9 +212,19 @@ void CreateDestroyTownInfoBox( void ) // resize box to fit button pDimensions.iRight += BOX_BUTTON_WIDTH; } - + pDimensions.iBottom += BOX_BUTTON_HEIGHT; + for ( UINT16 x = 0; x < MAX_NUMBER_OF_SAMS; ++x ) + { + if ( pSamList[x] == SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ) ) + { + pDimensions.iBottom += BOX_BUTTON_HEIGHT; + + break; + } + } + SetBoxSize( ghTownMineBox, pDimensions ); ShowBox( ghTownMineBox ); @@ -1004,13 +1019,9 @@ void PositionTownMineInfoBox( void ) { pPosition.iY = MapScreenRect.iBottom - pDimensions.iBottom - 8; } - - - + // reset position SetBoxPosition( ghTownMineBox, pPosition ); - - return; } @@ -1024,8 +1035,7 @@ void AddInventoryButtonForMapPopUpBox( void ) ETRLEObject *pTrav; INT16 sWidthA = 0, sWidthB = 0, sTotalBoxWidth = 0; HVOBJECT hHandle; - - + // load the button VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE; FilenameForBPP("INTERFACE\\mapinvbtns.sti", VObjectDesc.ImageFile); @@ -1070,23 +1080,39 @@ void AddInventoryButtonForMapPopUpBox( void ) (INT16)(sX ), (INT16)( sY ), BUTTON_TOGGLE , MSYS_PRIORITY_HIGHEST - 1, DEFAULT_MOVE_CALLBACK, (GUI_CALLBACK)MapTownMineExitButtonCallBack ); + // Flugente: if this is SAM site we control, we have the option to repair it if it is damagged + guiSAMButtonDefined = FALSE; + + for ( UINT16 x = 0; x < MAX_NUMBER_OF_SAMS; ++x ) + { + if ( pSamList[x] == SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ) ) + { + StrategicMapElement *pSAMStrategicMap = &(StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(pSamList[x])]); + + sX = pPosition.iX + (pDimensions.iRight - sTotalBoxWidth) / 3; + sY = pPosition.iY + pDimensions.iBottom - ((BOX_BUTTON_HEIGHT + 5)) - BOX_BUTTON_HEIGHT; + + guiMapButtonInventoryImage[2] = LoadButtonImage( "INTERFACE\\mapinvbtns.sti", -1, 1, -1, 3, -1 ); + + guiMapButtonInventory[2] = CreateIconAndTextButton( guiMapButtonInventoryImage[2], pMapPopUpInventoryText[2], BLOCKFONT2, + FONT_WHITE, FONT_BLACK, + FONT_WHITE, FONT_BLACK, + TEXT_CJUSTIFIED, + (INT16)(sX), (INT16)(sY), BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST - 1, + DEFAULT_MOVE_CALLBACK, (GUI_CALLBACK)MapTownSAMRepairButtonCallBack ); + + guiSAMButtonDefined = TRUE; + + break; + } + } + // delete video object DeleteVideoObjectFromIndex( uiObject ); - -/* - // if below ground disable - if( iCurrentMapSectorZ ) - { - DisableButton( guiMapButtonInventory[ 0 ] ); - } -*/ - - return; } void RemoveInventoryButtonForMapPopUpBox( void ) { - // get rid of button RemoveButton( guiMapButtonInventory[0] ); UnloadButtonImage( guiMapButtonInventoryImage[0] ); @@ -1094,7 +1120,11 @@ void RemoveInventoryButtonForMapPopUpBox( void ) RemoveButton( guiMapButtonInventory[1] ); UnloadButtonImage( guiMapButtonInventoryImage[1] ); - return; + if ( guiSAMButtonDefined ) + { + RemoveButton( guiMapButtonInventory[2] ); + UnloadButtonImage( guiMapButtonInventoryImage[2] ); + } } @@ -1102,13 +1132,13 @@ void MapTownMineInventoryButtonCallBack( GUI_BUTTON *btn, INT32 reason ) { if(reason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) { - btn->uiFlags|=(BUTTON_CLICKED_ON); + btn->uiFlags|=(BUTTON_CLICKED_ON); } else if(reason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { - if (btn->uiFlags & BUTTON_CLICKED_ON) + if (btn->uiFlags & BUTTON_CLICKED_ON) { - btn->uiFlags&=~(BUTTON_CLICKED_ON); + btn->uiFlags&=~(BUTTON_CLICKED_ON); // done fShowMapInventoryPool = TRUE; @@ -1126,7 +1156,6 @@ void MapTownMineInventoryButtonCallBack( GUI_BUTTON *btn, INT32 reason ) } } - void MapTownMineExitButtonCallBack( GUI_BUTTON *btn, INT32 reason ) { if(reason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) @@ -1148,6 +1177,99 @@ void MapTownMineExitButtonCallBack( GUI_BUTTON *btn, INT32 reason ) } } +void SAMRepairCallBack( UINT8 bExitValue ) +{ + if ( bExitValue == MSG_BOX_RETURN_YES ) + { + for ( UINT16 x = 0; x < MAX_NUMBER_OF_SAMS; ++x ) + { + if ( pSamList[x] == SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ) ) + { + StrategicMapElement *pSAMStrategicMap = &(StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX( pSamList[x] )]); + + if ( !pSAMStrategicMap->fEnemyControlled && pSAMStrategicMap->bSAMCondition < 100 ) + { + UINT32 cost = (100 - pSAMStrategicMap->bSAMCondition) * 100; + + AddTransactionToPlayersBook( SAM_REPAIR, 0, GetWorldTotalMin( ), -cost ); + + // remember this, so we don't accidentally pay for repair again + pSAMStrategicMap->usFlags |= SAMSITE_REPAIR_ORDERED; + + UINT32 time = (100 - pSAMStrategicMap->bSAMCondition) / 4 + 1; + + AddStrategicEvent( EVENT_SAMSITE_REPAIRED, GetWorldTotalMin( ) + 60 * time, pSamList[x] ); + } + + break; + } + } + } +} + +void MapTownSAMRepairButtonCallBack( GUI_BUTTON *btn, INT32 reason ) +{ + if ( reason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) + { + btn->uiFlags |= (BUTTON_CLICKED_ON); + } + else if ( reason & MSYS_CALLBACK_REASON_LBUTTON_UP ) + { + if ( btn->uiFlags & BUTTON_CLICKED_ON ) + { + btn->uiFlags &= ~(BUTTON_CLICKED_ON); + + // done + fMapPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; + fShowTownInfo = FALSE; + + for ( UINT16 x = 0; x < MAX_NUMBER_OF_SAMS; ++x ) + { + if ( pSamList[x] == SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ) ) + { + StrategicMapElement *pSAMStrategicMap = &(StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX( pSamList[x] )]); + + if ( pSAMStrategicMap->fEnemyControlled ) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, szEnemyHeliText[1], MAP_SCREEN, MSG_BOX_FLAG_OK, NULL ); + break; + } + + if ( pSAMStrategicMap->bSAMCondition >= 100 ) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, szEnemyHeliText[2], MAP_SCREEN, MSG_BOX_FLAG_OK, NULL ); + break; + } + + if ( pSAMStrategicMap->usFlags & SAMSITE_REPAIR_ORDERED ) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, szEnemyHeliText[3], MAP_SCREEN, MSG_BOX_FLAG_OK, NULL ); + break; + } + + UINT32 cost = (100 - pSAMStrategicMap->bSAMCondition) * 100; + + if ( cost > GetCurrentBalance( ) ) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, szEnemyHeliText[4], MAP_SCREEN, MSG_BOX_FLAG_OK, NULL ); + break; + } + + UINT32 time = (100 - pSAMStrategicMap->bSAMCondition) / 4 + 1; + + CHAR16 sSamSiteRepairPromptText[320]; + swprintf( sSamSiteRepairPromptText, szEnemyHeliText[5], cost, time ); + + // ask the player whether he really wants to do this + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sSamSiteRepairPromptText, MAP_SCREEN, MSG_BOX_FLAG_YESNO, SAMRepairCallBack ); + + break; + } + } + } + } +} // get the min width of the town mine info pop up box void MinWidthOfTownMineInfoBox( void ) diff --git a/Strategic/Player Command.cpp b/Strategic/Player Command.cpp index 0778c562..9a714639 100644 --- a/Strategic/Player Command.cpp +++ b/Strategic/Player Command.cpp @@ -542,6 +542,13 @@ BOOLEAN SetThisSectorAsEnemyControlled( INT16 sMapX, INT16 sMapY, INT8 bMapZ, BO return fWasPlayerControlled; } +BOOLEAN IsSectorEnemyControlled( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) +{ + if ( !bMapZ ) + return StrategicMap[SECTOR( sMapX, sMapY )].fEnemyControlled; + + return !SectorInfo[SECTOR( sMapX, sMapY )].fPlayer[bMapZ]; +} #ifdef JA2TESTVERSION void ClearMapControlledFlags( void ) diff --git a/Strategic/Player Command.h b/Strategic/Player Command.h index c0da116a..c4bcee2f 100644 --- a/Strategic/Player Command.h +++ b/Strategic/Player Command.h @@ -14,6 +14,7 @@ BOOLEAN SetThisSectorAsEnemyControlled( INT16 sMapX, INT16 sMapY, INT8 bMapZ, BO // set sector as player controlled BOOLEAN SetThisSectorAsPlayerControlled( INT16 sMapX, INT16 sMapY, INT8 bMapZ, BOOLEAN fContested ); +BOOLEAN IsSectorEnemyControlled( INT16 sMapX, INT16 sMapY, INT8 bMapZ ); #ifdef JA2TESTVERSION void ClearMapControlledFlags( void ); diff --git a/Strategic/PreBattle Interface.cpp b/Strategic/PreBattle Interface.cpp index e913adaf..2de07916 100644 --- a/Strategic/PreBattle Interface.cpp +++ b/Strategic/PreBattle Interface.cpp @@ -181,6 +181,8 @@ UINT8 gubEnemyEncounterCode = NO_ENCOUNTER_CODE; //would turn hostile, which would disable the ability to autoresolve the battle. BOOLEAN gubExplicitEnemyEncounterCode = NO_ENCOUNTER_CODE; +BOOLEAN gubSpecialEncounterCodeForEnemyHeli = FALSE; + //Location of the current battle (determines where the animated icon is blitted) and if the //icon is to be blitted. BOOLEAN gfBlitBattleSectorLocator = FALSE; @@ -607,8 +609,15 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) { if( !pBattleGroup ) { - //creature's attacking! - gubEnemyEncounterCode = CREATURE_ATTACK_CODE; + if ( gubSpecialEncounterCodeForEnemyHeli ) + { + gubEnemyEncounterCode = ENEMY_INVASION_CODE; + } + else + { + //creature's attacking! + gubEnemyEncounterCode = CREATURE_ATTACK_CODE; + } } else if ( gpBattleGroup->usGroupTeam == OUR_TEAM ) { @@ -929,6 +938,9 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) DisableButton( iPBButton[0] ); #endif DoTransitionFromMapscreenToPreBattleInterface(); + + // clean up + gubSpecialEncounterCodeForEnemyHeli = FALSE; } void DoTransitionFromMapscreenToPreBattleInterface() diff --git a/Strategic/PreBattle Interface.h b/Strategic/PreBattle Interface.h index ad2a4bf2..bd771f9b 100644 --- a/Strategic/PreBattle Interface.h +++ b/Strategic/PreBattle Interface.h @@ -81,6 +81,8 @@ extern UINT8 gubPBSectorY; extern UINT8 gubPBSectorZ; extern BOOLEAN gfCantRetreatInPBI; + +extern BOOLEAN gubSpecialEncounterCodeForEnemyHeli; //SAVE END void WakeUpAllMercsInSectorUnderAttack( void ); diff --git a/Strategic/Queen Command.cpp b/Strategic/Queen Command.cpp index 8e077c56..2d5e8f1e 100644 --- a/Strategic/Queen Command.cpp +++ b/Strategic/Queen Command.cpp @@ -44,6 +44,7 @@ #include "Soldier macros.h" #include "Morale.h" #include "CampaignStats.h" // added by Flugente + #include "ASD.h" // added by Flugente #endif #ifdef JA2BETAVERSION @@ -1559,11 +1560,17 @@ void AddPossiblePendingEnemiesToBattle() return; } } - + if ( (!PlayerMercsInSector( (UINT8)gWorldSectorX, (UINT8)gWorldSectorY, 0 ) && !NumNonPlayerTeamMembersInSector( gWorldSectorX, gWorldSectorY, MILITIA_TEAM )) || !NumNonPlayerTeamMembersInSector( gWorldSectorX, gWorldSectorY, ENEMY_TEAM ) ) return; + // Flugente: if there is an enemy helicopter here, it can add reinforcements + if ( gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition ) + { + EnemyHeliTroopDrop( SECTOR( gWorldSectorX, gWorldSectorY ) ); + } + ubSlots = NumFreeSlots( ENEMY_TEAM ); if(gGameExternalOptions.sMinDelayEnemyReinforcements)//dnl ch68 080913 { diff --git a/Strategic/Strategic AI.cpp b/Strategic/Strategic AI.cpp index d46c9ec7..727b089c 100644 --- a/Strategic/Strategic AI.cpp +++ b/Strategic/Strategic AI.cpp @@ -35,6 +35,7 @@ #include "Scheduling.h" #include "Map Information.h" #include "interface dialogue.h" + #include "ASD.h" // added by Flugente #endif #include "GameInitOptionsScreen.h" @@ -1489,33 +1490,30 @@ void InitStrategicAI() //Some of the patrol groups aren't there at the beginning of the game. This is //based on the difficulty settings in the above patrol table. //if( gPatrolGroup[ i ].ubUNUSEDStartIfDifficulty <= gGameOptions.ubDifficultyLevel ) - { //Add this patrol group now. + { + //Add this patrol group now. ubNumTroops = (UINT8)(gPatrolGroup[ i ].bSize + Random( 3 ) - 1); ubNumTroops = (UINT8)max( gubMinEnemyGroupSize, min( iMaxEnemyGroupSize, ubNumTroops ) ); ubNumTanks = 0; - if( ubNumTroops && gGameExternalOptions.fArmyUsesTanksInPatrols && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) - { + if ( ubNumTroops && gGameExternalOptions.fArmyUsesTanksInPatrols && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) + { + UINT32 val2; + if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_EASY ) + val2 = 1; + else if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_MEDIUM ) + val2 = 2; + else if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_HARD ) + val2 = 3; + else if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE ) + val2 = 4; + else + { + val2 = Random( 4 ); + if (val2 == 0) val2 = 1; + } - UINT32 val2; - if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_EASY ) - val2 = 1; - else if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_MEDIUM ) - val2 = 2; - else if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_HARD ) - val2 = 3; - else if( gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE ) - val2 = 4; - else - { - val2 = Random( 4 ); - if (val2 == 0) val2 = 1; - } - - if( Random( 10 ) < val2 && i != 3 && i != 4) - - //if( Random( 10 ) < gGameOptions.ubDifficultyLevel && i != 3 && i != 4) - + if ( Random( 10 ) < val2 && i != 3 && i != 4 ) { ubNumTroops--; ubNumTanks++; @@ -2229,14 +2227,17 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Strategic5"); pSector->ubNumElites = (UINT8)(pSector->ubNumElites + pGroup->pEnemyGroup->ubNumElites); pSector->ubNumTanks = (UINT8)(pSector->ubNumTanks + pGroup->pEnemyGroup->ubNumTanks); - #ifdef JA2BETAVERSION - LogStrategicEvent( "%d reinforcements have arrived to garrison sector %c%d", - pGroup->pEnemyGroup->ubNumAdmins + pGroup->pEnemyGroup->ubNumTroops + - pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumTanks, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX ); - #endif +#ifdef JA2BETAVERSION + LogStrategicEvent( "%d reinforcements have arrived to garrison sector %c%d", + pGroup->pEnemyGroup->ubNumAdmins + pGroup->pEnemyGroup->ubNumTroops + + pGroup->pEnemyGroup->ubNumElites + pGroup->pEnemyGroup->ubNumTanks, pGroup->ubSectorY + 'A' - 1, pGroup->ubSectorX ); +#endif + if( IsThisSectorASAMSector( pGroup->ubSectorX, pGroup->ubSectorY, 0 ) ) { StrategicMap[ pGroup->ubSectorX + pGroup->ubSectorY * MAP_WORLD_X ].bSAMCondition = 100; + StrategicMap[ pGroup->ubSectorX + pGroup->ubSectorY * MAP_WORLD_X ].usFlags &= ~SAMSITE_REPAIR_ORDERED; + UpdateSAMDoneRepair( pGroup->ubSectorX, pGroup->ubSectorY, 0 ); } } @@ -4551,8 +4552,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto grouptanks[ubCounter] = 0; if( ubNumSoldiers && gGameExternalOptions.fArmyUsesTanksInAttacks && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) { - if( Random(10) < val ) - //if( Random(10) < gGameOptions.ubDifficultyLevel ) + if ( Random( 10 ) < val && ASDSoldierUpgradeToTank( ) ) { grouptroops[ubCounter]--; grouptanks[ubCounter]++; @@ -4636,7 +4636,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto grouptanks[ubCounter] = 0; if( ubNumSoldiers && gGameExternalOptions.fArmyUsesTanksInAttacks && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) { - if( Random(10) < value ) + if ( Random( 10 ) < value && ASDSoldierUpgradeToTank( ) ) { grouptroops[ubCounter]--; grouptanks[ubCounter]++; @@ -4848,7 +4848,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto ubNumSoldiers = (UINT8)( gubMinEnemyGroupSize + value2 * 3); //ubNumSoldiers = (UINT8)( gubMinEnemyGroupSize + gGameOptions.ubDifficultyLevel * 3); // anv: replace one of soldiers with tank - if( ubNumSoldiers && gGameExternalOptions.fArmyUsesTanksInAttacks && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) + if ( ubNumSoldiers && gGameExternalOptions.fArmyUsesTanksInAttacks && HighestPlayerProgressPercentage( ) >= gGameExternalOptions.usTankMinimumProgress && ASDSoldierUpgradeToTank( ) ) { ubNumSoldiers--; ubNumTanks++; @@ -5114,8 +5114,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto // anv: replace one of soldiers with tank if( ubNumSoldiers && gGameExternalOptions.fArmyUsesTanksInAttacks && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) { - if( Random( 10 ) < value4 ) - //if( Random( 10 ) < gGameOptions.ubDifficultyLevel ) + if ( Random( 10 ) < value4 && ASDSoldierUpgradeToTank( ) ) { ubNumSoldiers--; ubNumTanks++; @@ -5242,8 +5241,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto if( ubNumSoldiers && gGameExternalOptions.fArmyUsesTanksInAttacks && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) { - if( Random( 10 * 100 ) < value5 * direness ) - //if( Random( 10 * 100 ) < gGameOptions.ubDifficultyLevel * direness ) + if ( Random( 10 * 100 ) < value5 * direness && ASDSoldierUpgradeToTank( ) ) { elitesThisSquad--; tanksThisSquad++; @@ -5398,8 +5396,7 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto // anv: replace one of soldiers with tank if( ubNumSoldiers && gGameExternalOptions.fArmyUsesTanksInAttacks && HighestPlayerProgressPercentage() >= gGameExternalOptions.usTankMinimumProgress ) { - if( Random( 10 * 100 ) < value7 * HighestPlayerProgressPercentage() ) - //if( Random( 10 * 100 ) < gGameOptions.ubDifficultyLevel * HighestPlayerProgressPercentage() ) + if ( Random( 10 * 100 ) < value7 * HighestPlayerProgressPercentage( ) && ASDSoldierUpgradeToTank( ) ) { ubNumSoldiers--; ubNumTanks++; diff --git a/Strategic/Strategic Mines.cpp b/Strategic/Strategic Mines.cpp index 5c6c991f..e2d7c855 100644 --- a/Strategic/Strategic Mines.cpp +++ b/Strategic/Strategic Mines.cpp @@ -22,6 +22,7 @@ #include "Campaign Types.h" #include "history.h" #include "Facilities.h" + #include "ASD.h" // added by Flugente #endif #include "GameInitOptionsScreen.h" @@ -549,17 +550,14 @@ INT32 GetAvailableWorkForceForMineForEnemy( INT8 bMineIndex ) // if mine is shut down if ( gMineStatus[ bMineIndex ].fShutDown) - { return ( 0 ); - } - //bTownId = gMineLocation[ bMineIndex ].bAssociatedTown; bTownId = gMineStatus[ bMineIndex ].bAssociatedTown; - if( GetTownSectorSize( bTownId ) == 0 ) - { + UINT8 townsize = GetTownSectorSize( bTownId ); + + if ( !townsize ) return 0; - } // get workforce size (is 0-100 based on REVERSE of local town's loyalty) iWorkForceSize = 100 - gTownLoyalty[ bTownId ].ubRating; @@ -571,10 +569,10 @@ INT32 GetAvailableWorkForceForMineForEnemy( INT8 bMineIndex ) */ // now adjust for town size.. the number of sectors you control - iWorkForceSize *= ( GetTownSectorSize( bTownId ) - GetTownSectorsUnderControl( bTownId ) ); - iWorkForceSize /= GetTownSectorSize( bTownId ); + iWorkForceSize *= (townsize - GetTownSectorsUnderControl( bTownId )); + iWorkForceSize /= townsize; - return ( iWorkForceSize ); + return iWorkForceSize; } INT32 GetCurrentWorkRateOfMineForPlayer( INT8 bMineIndex ) @@ -604,8 +602,7 @@ INT32 MineAMine( INT8 bMineIndex ) // will extract ore based on available workforce, and increment players income based on amount INT8 bMineType = 0; INT32 iAmtExtracted = 0; - - + Assert( ( bMineIndex >= 0 ) && ( bMineIndex < MAX_NUMBER_OF_MINES ) ); // is mine is empty @@ -620,7 +617,6 @@ INT32 MineAMine( INT8 bMineIndex ) return 0; } - // who controls the PRODUCTION in the mine ? (Queen receives production unless player has spoken to the head miner) if( PlayerControlsMine(bMineIndex) ) { @@ -651,13 +647,16 @@ INT32 MineAMine( INT8 bMineIndex ) // we didn't want mines to run out without player ever even going to them, so now the queen doesn't reduce the // amount remaining until the mine has produced for the player first (so she'd have to capture it). - // WANNE: We do not want to give money to the player, when the queen has captured the mine! + /*// WANNE: We do not want to give money to the player, when the queen has captured the mine! if ( gMineStatus[ bMineIndex ].fMineHasProducedForPlayer ) { // don't actually give her money, just take production away //iAmtExtracted = ExtractOreFromMine( bMineIndex , GetCurrentWorkRateOfMineForEnemy( bMineIndex ) ); iAmtExtracted = 0; - } + }*/ + + // Flugente: if the enemy controls a mine, simply don't lower the amount of ore in the mine, so it can still run out when the player controls it + iAmtExtracted = GetCurrentWorkRateOfMineForEnemy( bMineIndex ); } @@ -679,27 +678,42 @@ void PostEventsForMineProduction(void) void HandleIncomeFromMines( void ) { INT32 iIncome = 0; - INT8 bCounter = 0; + INT32 iIncome_Enemy = 0; // Flugente: new AI gets money from mines + // HEADROCK HAM 3.6: Modifier from Facilities INT32 iFacilityModifier = 0; // mine each mine, check if we own it and such - for( bCounter = 0; bCounter < MAX_NUMBER_OF_MINES; bCounter++ ) + for ( INT8 bCounter = 0; bCounter < MAX_NUMBER_OF_MINES; ++bCounter ) { - if (bCounter) + if ( PlayerControlsMine( bCounter ) ) { // HEADROCK HAM 3.6: Add facility modifier as a percentage iFacilityModifier = 100 + MineIncomeModifierFromFacility( bCounter ); + + // mine this mine + iIncome += (MineAMine( bCounter ) * iFacilityModifier) / 100; + } + else + { + // mine this mine + iIncome_Enemy += MineAMine( bCounter ); } - // mine this mine - iIncome += (MineAMine( bCounter ) * iFacilityModifier) / 100; } + // HEADROCK HAM B1: Affected by external INI Option. iIncome = (iIncome * gGameExternalOptions.usMineIncomePercentage) / 100; + iIncome_Enemy = (iIncome_Enemy * gGameExternalOptions.usMineIncomePercentage) / 100; + if( iIncome ) { AddTransactionToPlayersBook( DEPOSIT_FROM_SILVER_MINE, 0, GetWorldTotalMin( ), iIncome ); } + + if ( iIncome_Enemy ) + { + AddStrategicAIResources( ASD_MONEY, iIncome_Enemy ); + } } diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index 6a9223d5..f915e8f6 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -50,6 +50,7 @@ #include "Interface.h" // added by Flugente for zBackground #include "Reinforcement.h" // added by Flugente for AddPossiblePendingMilitiaToBattle() #include "Militia Control.h" // added by Flugente for ResetMilitia() + #include "Creature Spreading.h" // added by Flugente #endif #include "MilitiaSquads.h" @@ -5940,3 +5941,195 @@ GROUP* CreateNewEnemyGroupDepartingFromSectorUsingZLevel( UINT32 uiSector, UINT8 #endif + +void CheckCombatInSectorDueToUnusualEnemyArrival( UINT8 aTeam, INT16 sX, INT16 sY, INT8 sZ ) +{ + GROUP *curr; + GROUP *pPlayerDialogGroup = NULL; + PLAYERGROUP *pPlayer; + SOLDIERTYPE *pSoldier; + BOOLEAN fBattlePending = FALSE; + BOOLEAN fAliveMerc = FALSE; + BOOLEAN fMilitiaPresent = FALSE; + BOOLEAN fCombatAbleMerc = FALSE; + BOOLEAN fBloodCatAmbush = FALSE; + + gubEnemyEncounterCode = ENEMY_INVASION_CODE; + + gubSectorIDOfCreatureAttack = SECTOR(sX, sY); + + gubSpecialEncounterCodeForEnemyHeli = TRUE; + + HandleOtherGroupsArrivingSimultaneously( sX, sY, sZ ); + + curr = gpGroupList; + while ( curr ) + { + if ( curr->usGroupTeam == OUR_TEAM && curr->ubGroupSize ) + { + if ( !curr->fBetweenSectors ) + { + if ( curr->ubSectorX == sX && curr->ubSectorY == sY && !sZ ) + { + if ( !GroupHasInTransitDeadOrPOWMercs( curr ) && + (!IsGroupTheHelicopterGroup( curr ) || !fHelicopterIsAirBorne) && + (!curr->fVehicle || NumberMercsInVehicleGroup( curr )) ) + { + //Now, a player group is in this sector. Determine if the group contains any mercs that can fight. + //Vehicles, EPCs and the robot doesn't count. Mercs below OKLIFE do. + pPlayer = curr->pPlayerList; + while ( pPlayer ) + { + pSoldier = pPlayer->pSoldier; + if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) ) + { + if ( !AM_A_ROBOT( pSoldier ) && + !AM_AN_EPC( pSoldier ) && + pSoldier->stats.bLife >= OKLIFE ) + { + fCombatAbleMerc = TRUE; + } + if ( pSoldier->stats.bLife > 0 ) + { + fAliveMerc = TRUE; + } + } + pPlayer = pPlayer->next; + } + + if ( !pPlayerDialogGroup && fCombatAbleMerc ) + { + pPlayerDialogGroup = curr; + } + + if ( fCombatAbleMerc ) + { + break; + } + } + } + } + } + curr = curr->next; + } + + if ( aTeam == ENEMY_TEAM ) + { + if ( NumNonPlayerTeamMembersInSector( sX, sY, MILITIA_TEAM ) ) + { + fMilitiaPresent = TRUE; + fBattlePending = TRUE; + } + + if ( fAliveMerc ) + { + fBattlePending = TRUE; + } + + // huh? + if ( !NumNonPlayerTeamMembersInSector( sX, sY, ENEMY_TEAM ) ) + { + fBattlePending = FALSE; + } + } + else if ( aTeam == MILITIA_TEAM ) + { + if ( NumNonPlayerTeamMembersInSector( sX, sY, ENEMY_TEAM ) ) + { + fMilitiaPresent = TRUE; + fBattlePending = TRUE; + } + + /*if ( fBattlePending ) + { + if ( PossibleToCoordinateSimultaneousGroupArrivals( pGroup ) ) + { + return FALSE; + } + }*/ + } + + if ( !fAliveMerc && !fMilitiaPresent ) + { + // empty vehicle, everyone dead, don't care. Enemies don't care. + return; + } + + if ( fBattlePending ) + { + //A battle is pending, but the players could be all unconcious or dead. + //Go through every group until we find at least one concious merc. The looping will determine + //if there are any live mercs and/or concious ones. If there are no concious mercs, but alive ones, + //then we will go straight to autoresolve, where the enemy will likely annihilate them or capture them. + //If there are no alive mercs, then there is nothing anybody can do. The enemy will completely ignore + //this, and continue on. + + StopTimeCompression( ); + + if ( gubNumGroupsArrivedSimultaneously ) + { + //Because this is a battle case, clear all the group flags + curr = gpGroupList; + while ( curr && gubNumGroupsArrivedSimultaneously ) + { + if ( curr->uiFlags & GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY ) + { + curr->uiFlags &= ~GROUPFLAG_GROUP_ARRIVED_SIMULTANEOUSLY; + gubNumGroupsArrivedSimultaneously--; + } + curr = curr->next; + } + } + + //gpInitPrebattleGroup = pGroup; + + if ( gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE || gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE ) + { + NotifyPlayerOfBloodcatBattle( sX, sY ); + + return; + } + + if ( !fCombatAbleMerc ) + { + //Prepare for instant autoresolve. + gfDelayAutoResolveStart = TRUE; + gfUsePersistantPBI = TRUE; + if ( fMilitiaPresent ) + { + NotifyPlayerOfInvasionByEnemyForces( sX, sY, 0, TriggerPrebattleInterface ); + + // trigger autoresolve if not in city, or this is a militia group + if ( aTeam == MILITIA_TEAM || GetTownIdForSector( sX, sY ) == BLANK_SECTOR ) + { + // CHAR16 str[ 256 ]; + // UINT16 uiSectorC = L'A' + pGroup->ubSectorY - 1; + // swprintf( str, gpStrategicString[ STR_DIALOG_ENEMIES_ATTACK_MILITIA ], uiSectorC, pGroup->ubSectorX ); + // DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, TriggerPrebattleInterface ); + TriggerPrebattleInterface( 1 ); + } + } + else + { + CHAR16 str[256]; + CHAR16 pSectorStr[128]; + GetSectorIDString( sX, sY, sZ, pSectorStr, TRUE ); + swprintf( str, gpStrategicString[STR_DIALOG_ENEMIES_ATTACK_UNCONCIOUSMERCS], pSectorStr ); + DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, TriggerPrebattleInterface ); + } + } + +#ifdef JA2BETAVERSION + if ( guiCurrentScreen == AIVIEWER_SCREEN ) + gfExitViewer = TRUE; +#endif + + if ( pPlayerDialogGroup ) + { + if ( !sZ ) + MilitiaHelpFromAdjacentSectors( sX, sY ); + + PrepareForPreBattleInterface( pPlayerDialogGroup, NULL ); + } + } +} diff --git a/Strategic/Strategic Movement.h b/Strategic/Strategic Movement.h index bcb155ab..ea86c3af 100644 --- a/Strategic/Strategic Movement.h +++ b/Strategic/Strategic Movement.h @@ -316,4 +316,6 @@ BOOLEAN GroupHasInTransitDeadOrPOWMercs( GROUP *pGroup ); BOOLEAN ScoutIsPresentInSquad( INT16 ubSectorNumX, INT16 ubSectorNumY ); // added by SANDRO +void CheckCombatInSectorDueToUnusualEnemyArrival( UINT8 aTeam, INT16 sX, INT16 sY, INT8 sZ ); + #endif diff --git a/Strategic/Strategic_VS2005.vcproj b/Strategic/Strategic_VS2005.vcproj index fb6d2b61..2417a4ee 100644 --- a/Strategic/Strategic_VS2005.vcproj +++ b/Strategic/Strategic_VS2005.vcproj @@ -338,6 +338,10 @@ Filter="h;hpp;hxx;hm;inl;inc;xsd" UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > + + @@ -544,6 +548,10 @@ RelativePath=".\AI Viewer.cpp" > + + diff --git a/Strategic/Strategic_VS2008.vcproj b/Strategic/Strategic_VS2008.vcproj index 45a30fe5..9f6af175 100644 --- a/Strategic/Strategic_VS2008.vcproj +++ b/Strategic/Strategic_VS2008.vcproj @@ -338,6 +338,10 @@ + + @@ -542,6 +546,10 @@ RelativePath="AI Viewer.cpp" > + + diff --git a/Strategic/Strategic_VS2010.vcxproj b/Strategic/Strategic_VS2010.vcxproj index f8eeeb7d..ea126dc8 100644 --- a/Strategic/Strategic_VS2010.vcxproj +++ b/Strategic/Strategic_VS2010.vcxproj @@ -23,6 +23,7 @@ + @@ -75,6 +76,7 @@ + diff --git a/Strategic/Strategic_VS2010.vcxproj.filters b/Strategic/Strategic_VS2010.vcxproj.filters index ac5bc951..a5345c5e 100644 --- a/Strategic/Strategic_VS2010.vcxproj.filters +++ b/Strategic/Strategic_VS2010.vcxproj.filters @@ -9,6 +9,9 @@ + + Header Files + Header Files @@ -161,6 +164,9 @@ Source Files + + Source Files + Source Files diff --git a/Strategic/Strategic_VS2013.vcxproj b/Strategic/Strategic_VS2013.vcxproj index 05ae13f5..208a5e51 100644 --- a/Strategic/Strategic_VS2013.vcxproj +++ b/Strategic/Strategic_VS2013.vcxproj @@ -23,6 +23,7 @@ + @@ -75,6 +76,7 @@ + diff --git a/Strategic/Strategic_VS2013.vcxproj.filters b/Strategic/Strategic_VS2013.vcxproj.filters index ac5bc951..838a6880 100644 --- a/Strategic/Strategic_VS2013.vcxproj.filters +++ b/Strategic/Strategic_VS2013.vcxproj.filters @@ -156,6 +156,9 @@ Header Files + + Header Files + @@ -344,5 +347,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/Strategic/Town Militia.cpp b/Strategic/Town Militia.cpp index 4f0580c4..0adeb375 100644 --- a/Strategic/Town Militia.cpp +++ b/Strategic/Town Militia.cpp @@ -1325,6 +1325,7 @@ BOOLEAN CanSomeoneNearbyScoutThisSector( INT16 sSectorX, INT16 sSectorY, BOOLEAN { return( TRUE ); } + // SANDRO - STOMP traits - Scouting check if (fScoutTraitCheck && gGameOptions.fNewTraitSystem && ScoutIsPresentInSquad( sCounterA, sCounterB )) { @@ -1339,13 +1340,13 @@ BOOLEAN CanSomeoneNearbyScoutThisSector( INT16 sSectorX, INT16 sSectorY, BOOLEAN } else { - bScout = TRUE; + return TRUE; } } } } - return( bScout ); + return FALSE; } BOOLEAN IsTownFullMilitia( INT8 bTownId, INT8 iMilitiaType ) @@ -2477,13 +2478,8 @@ FLOAT CalcHourlyVolunteerGain() UINT8 sector = SECTOR( sX, sY ); - SECTORINFO *pSectorInfo = &(SectorInfo[sector]); - - if ( !pSectorInfo ) - continue; - - // TODO: modifier increase for every farm we control - if ( pSectorInfo->ubTraversability[THROUGH_STRATEGIC_MOVE] == FARMLAND || pSectorInfo->ubTraversability[THROUGH_STRATEGIC_MOVE] == FARMLAND_ROAD ) + // modifier increase for every farm we control + if ( IsSectorFarm( sX, sY ) ) populationmodifier += gGameExternalOptions.dMilitiaVolunteerMultiplierFarm; UINT8 ubTownID = StrategicMap[CALCULATE_STRATEGIC_INDEX( sX, sY )].bNameId; diff --git a/Strategic/mapscreen.cpp b/Strategic/mapscreen.cpp index 166f44d4..3691f458 100644 --- a/Strategic/mapscreen.cpp +++ b/Strategic/mapscreen.cpp @@ -5012,6 +5012,15 @@ UINT32 MapScreenHandle(void) CHECKF(AddVideoObject(&VObjectDesc, &guiHelicopterIcon)); + if ( iResolution >= _640x480 && iResolution < _800x600 ) + FilenameForBPP( "INTERFACE\\Helicopter_Map_Icon_Enemy_640.sti", VObjectDesc.ImageFile ); + else if ( iResolution < _1024x768 ) + FilenameForBPP( "INTERFACE\\Helicopter_Map_Icon_Enemy_800.sti", VObjectDesc.ImageFile ); + else + FilenameForBPP( "INTERFACE\\Helicopter_Map_Icon_Enemy_1024.sti", VObjectDesc.ImageFile ); + + CHECKF( AddVideoObject( &VObjectDesc, &guiEnemyHelicopterIcon ) ); + VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP("INTERFACE\\eta_pop_up.sti", VObjectDesc.ImageFile); CHECKF(AddVideoObject(&VObjectDesc, &guiMapBorderEtaPopUp)); @@ -5638,6 +5647,11 @@ UINT32 MapScreenHandle(void) DisplayPositionOfHelicopter( ); } + // Flugente: enemy helicopter + if ( fShowAircraftFlag && (iCurrentMapSectorZ == 0) && !fShowMapInventoryPool ) + { + DisplayPositionOfEnemyHelicopter(); + } // display town info DisplayTownInfo( sSelMapX, sSelMapY, ( INT8 ) iCurrentMapSectorZ ); @@ -8686,6 +8700,7 @@ INT32 iCounter2 = 0; DeleteVideoObjectFromIndex( guiMapBorderHeliSectors ); DeleteVideoObjectFromIndex( guiMapBorderHeliSectorsAlternate ); DeleteVideoObjectFromIndex( guiHelicopterIcon ); + DeleteVideoObjectFromIndex( guiEnemyHelicopterIcon ); DeleteVideoObjectFromIndex( guiMINEICON ); DeleteVideoObjectFromIndex( guiSectorLocatorGraphicID ); @@ -13552,6 +13567,7 @@ void HandleRemovalOfPreLoadedMapGraphics( void ) DeleteVideoObjectFromIndex( guiMapBorderHeliSectors ); DeleteVideoObjectFromIndex( guiMapBorderHeliSectorsAlternate ); DeleteVideoObjectFromIndex( guiHelicopterIcon ); + DeleteVideoObjectFromIndex( guiEnemyHelicopterIcon ); DeleteVideoObjectFromIndex( guiMINEICON ); DeleteVideoObjectFromIndex( guiSectorLocatorGraphicID ); diff --git a/Strategic/strategic.h b/Strategic/strategic.h index 1bd029e4..d7a591d2 100644 --- a/Strategic/strategic.h +++ b/Strategic/strategic.h @@ -52,7 +52,8 @@ enum #define MILITIA_MOVE_SOUTH 0x00000008 //8 #define ENEMY_VIP_PRESENT 0x00000010 //16 // an enemy VIP is present in this sector -#define ENEMY_VIP_PRESENT_KNOWN 0x00000020 //16 // player thinks a VIP is here +#define ENEMY_VIP_PRESENT_KNOWN 0x00000020 //32 // player thinks a VIP is here +#define SAMSITE_REPAIR_ORDERED 0x00000040 //64 // a repair for this SAM site has already been ordered (so we do not need to do that again) #define MILITIA_MOVE_ALLDIRS 0x0000000F // 15 // ------------------------------------------------------- diff --git a/Strategic/strategicmap.cpp b/Strategic/strategicmap.cpp index 1e5090ae..2e310b34 100644 --- a/Strategic/strategicmap.cpp +++ b/Strategic/strategicmap.cpp @@ -138,6 +138,7 @@ #include "sgp_logger.h" #include "Map Screen Interface Map Inventory.h" // added by Flugente +#include "ASD.h" // added by Flugente //forward declarations of common classes to eliminate includes class OBJECTTYPE; @@ -2153,6 +2154,8 @@ BOOLEAN SetCurrentWorldSector( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) // Check for helicopter being on the ground in this sector... HandleHelicopterOnGroundGraphic( ); + HandleEnemyHelicopterOnGroundGraphic(); + // 0verhaul: Okay, it is apparent that the enemies are not reset correctly. So I will now try to add symmetry // between enemy placement and militia placement. The enemies do have one advantage here, though: If a sector // is in enemy hands, then their sector is not actually loaded. At least not normally. Perhaps it would be useful @@ -2304,6 +2307,8 @@ BOOLEAN SetCurrentWorldSector( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) // Check for helicopter being on the ground in this sector... HandleHelicopterOnGroundGraphic( ); + + HandleEnemyHelicopterOnGroundGraphic(); } else return( FALSE ); @@ -4994,7 +4999,7 @@ void SetupNewStrategicGame( ) AddEveryDayStrategicEvent( EVENT_DAILY_UPDATE_BOBBY_RAY_INVENTORY, BOBBYRAY_UPDATE_TIME, 0 ); //Daily Update of the M.E.R.C. site. AddEveryDayStrategicEvent( EVENT_DAILY_UPDATE_OF_MERC_SITE, 0, 0 ); - + #ifdef JA2UB //Ja25: No insurance for mercs //JA25: There is no mines @@ -5021,6 +5026,9 @@ void SetupNewStrategicGame( ) // Hourly update of all sorts of things AddPeriodStrategicEvent( EVENT_HOURLY_UPDATE, 60, 0 ); AddPeriodStrategicEvent( EVENT_QUARTER_HOUR_UPDATE, 15, 0 ); + + // Flugente: ASD + AddPeriodStrategicEvent( EVENT_ASD_UPDATE, 90, 0 ); //Clear any possible battle locator gfBlitBattleSectorLocator = FALSE; @@ -6179,6 +6187,7 @@ BOOLEAN IsSectorDesert( INT16 sSectorX, INT16 sSectorY ) return( FALSE ); } } + // SANDRO - added function BOOLEAN IsSectorTropical( INT16 sSectorX, INT16 sSectorY ) { @@ -6188,12 +6197,38 @@ BOOLEAN IsSectorTropical( INT16 sSectorX, INT16 sSectorY ) { return ( TRUE ); } - else - { - return ( FALSE ); - } + + return ( FALSE ); } +BOOLEAN IsSectorFarm( INT16 sSectorX, INT16 sSectorY ) +{ + if ( SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == FARMLAND || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == FARMLAND_ROAD ) + { + return (TRUE); + } + + return (FALSE); +} + +BOOLEAN IsSectorRoad( INT16 sSectorX, INT16 sSectorY ) +{ + if ( SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == PLAINS_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == SPARSE_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == FARMLAND_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == TROPICS_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == DENSE_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == HILLS_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == COASTAL_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == SAND_ROAD || + SectorInfo[SECTOR( sSectorX, sSectorY )].ubTraversability[THROUGH_STRATEGIC_MOVE] == SWAMP_ROAD ) + { + return (TRUE); + } + + return (FALSE); +} BOOLEAN HandleDefiniteUnloadingOfWorld( UINT8 ubUnloadCode ) { diff --git a/Strategic/strategicmap.h b/Strategic/strategicmap.h index 46646a62..a2b0ad72 100644 --- a/Strategic/strategicmap.h +++ b/Strategic/strategicmap.h @@ -176,6 +176,8 @@ BOOLEAN IsThereAFunctionalSAMSiteInSector( INT16 sSectorX, INT16 sSectorY, INT8 BOOLEAN IsSectorDesert( INT16 sSectorX, INT16 sSectorY ); BOOLEAN IsSectorTropical( INT16 sSectorX, INT16 sSectorY ); // added by SANDRO +BOOLEAN IsSectorFarm( INT16 sSectorX, INT16 sSectorY ); // added by Flugente +BOOLEAN IsSectorRoad( INT16 sSectorX, INT16 sSectorY ); // added by Flugente // sam site under players control? INT32 SAMSitesUnderPlayerControl( INT16 sX, INT16 sY ); diff --git a/Tactical/Dialogue Control.cpp b/Tactical/Dialogue Control.cpp index 0875af60..45a0f106 100644 --- a/Tactical/Dialogue Control.cpp +++ b/Tactical/Dialogue Control.cpp @@ -3322,9 +3322,9 @@ void SayQuoteFromAnyBodyInSector( UINT16 usQuoteNum ) void SayQuoteFromAnyBodyInThisSector( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ, UINT16 usQuoteNum ) { // WDS - make number of mercenaries, etc. be configurable - std::vector ubMercsInSector (CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS, 0); - UINT8 ubNumMercs = 0; - UINT8 ubChosenMerc; + std::vector ubMercsInSector (CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS, 0); + UINT16 ubNumMercs = 0; + UINT16 ubChosenMerc; SOLDIERTYPE *pTeamSoldier; INT32 cnt; @@ -3342,16 +3342,17 @@ void SayQuoteFromAnyBodyInThisSector( INT16 sSectorX, INT16 sSectorY, INT8 bSect if( pTeamSoldier->sSectorX == sSectorX && pTeamSoldier->sSectorY == sSectorY && pTeamSoldier->bSectorZ == bSectorZ && !AM_AN_EPC( pTeamSoldier ) && !( pTeamSoldier->flags.uiStatusFlags & SOLDIER_GASSED ) && !(AM_A_ROBOT( pTeamSoldier )) && !pTeamSoldier->flags.fMercAsleep ) { ubMercsInSector[ ubNumMercs ] = (UINT8)cnt; - ubNumMercs++; + ++ubNumMercs; } } } DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("SayQuoteFromAnyBodyInThisSector: num mercs = %d",ubNumMercs)); + // If we are > 0 if ( ubNumMercs > 0 ) { - ubChosenMerc = (UINT8)Random( ubNumMercs ); + ubChosenMerc = (UINT16)Random( ubNumMercs ); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("SayQuoteFromAnyBodyInThisSector: chosen merc = %d",ubChosenMerc)); //// If we are air raid, AND red exists somewhere... @@ -3367,7 +3368,6 @@ void SayQuoteFromAnyBodyInThisSector( INT16 sSectorX, INT16 sSectorY, INT8 bSect // } //} - TacticalCharacterDialogue( MercPtrs[ ubMercsInSector[ ubChosenMerc ] ], usQuoteNum ); } } diff --git a/Tactical/Interface.h b/Tactical/Interface.h index c55b2bf6..7eddf36b 100644 --- a/Tactical/Interface.h +++ b/Tactical/Interface.h @@ -175,6 +175,7 @@ enum { BG_PERC_HEARING_NIGHT, BG_PERC_HEARING_DAY, BG_PERC_DISARM, + BG_PERC_SAM_CTH, // approaches BG_PERC_APPROACH_FRIENDLY, diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index 9b9daca5..1430e996 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -731,7 +731,7 @@ extern OBJECTTYPE gTempObject; // flags used for various item properties (easier than adding 32 differently named variables). DO NOT CHANGE THEM, UNLESS YOU KNOW WHAT YOU ARE DOING!!! // note that these should not be used to determine what kind of an attachment an item is, that is determined by attachmentclass and the AC_xxx flags above //#define EMPTY_SANDBAG 0x00000001 //1 -//#define FULL_SANDBAG 0x00000002 //2 +#define MANPAD 0x00000002 //2 // this item is a MAn-Portable Air-Defense System //#define SHOVEL 0x00000004 //4 // a shovel is used for filling sandbags and other building-related tasks //#define CONCERTINA 0x00000008 //8 diff --git a/Tactical/Merc Entering.cpp b/Tactical/Merc Entering.cpp index a087e7d0..92df6001 100644 --- a/Tactical/Merc Entering.cpp +++ b/Tactical/Merc Entering.cpp @@ -40,6 +40,7 @@ #include "Tactical Save.h" // HEADROCK HAM 3.5: Need this to see if enemies present at starting sector #include "Overhead.h" + #include "Map Information.h" // added by Flugente #endif #ifdef JA2UB @@ -386,6 +387,7 @@ BOOLEAN gfIngagedInDrop = FALSE; ANITILE *gpHeli; BOOLEAN gfFirstHeliRun; +extern BOOLEAN gfTacticalDoHeliRun; void HandleFirstHeliDropOfGame( ); @@ -479,13 +481,16 @@ void StartHelicopterRun() } -void HandleHeliDrop( ) +void HandleHeliDrop( BOOLEAN fPlayer ) { UINT8 ubScriptCode; UINT32 uiClock; INT32 iVol; INT32 cnt; ANITILE_PARAMS AniParams; + + if ( !fPlayer ) + return HandleEnemyAirdrop(); if ( gfHandleHeli ) { @@ -1021,3 +1026,426 @@ void HandleFirstHeliDropOfGame( ) // Send message to turn on ai again.... CharacterDialogueWithSpecialEvent( 0, 0, 0, DIALOGUE_TACTICAL_UI , FALSE , FALSE , DIALOGUE_SPECIAL_EVENT_ENABLE_AI ,0, 0 ); } + +UINT16 SpawnAirDropElite( INT32 sGridNo ) +{ + SOLDIERTYPE *pSoldier; + + // not underground! + if ( gbWorldSectorZ ) + return NOBODY; + + // hmm... + if ( !IsLocationSittable( sGridNo, 0 ) ) + return NOBODY; + + // Flugente hack + pSoldier = TacticalCreateEliteEnemy( ); + + //Add soldier strategic info, so it doesn't break the counters! + if ( pSoldier ) + { + if ( !gbWorldSectorZ ) + { + SECTORINFO *pSector = &SectorInfo[SECTOR( gWorldSectorX, gWorldSectorY )]; + switch ( pSoldier->ubSoldierClass ) + { + case SOLDIER_CLASS_ADMINISTRATOR: pSector->ubNumAdmins++; pSector->ubAdminsInBattle++; break; + case SOLDIER_CLASS_ARMY: pSector->ubNumTroops++; pSector->ubTroopsInBattle++; break; + case SOLDIER_CLASS_ELITE: pSector->ubNumElites++; pSector->ubElitesInBattle++; break; + } + } + + pSoldier->ubStrategicInsertionCode = INSERTION_CODE_CHOPPER; + pSoldier->usStrategicInsertionData = sGridNo; // required, otherwise soldiers will spawn in map before jumping out of the heli + UpdateMercInSector( pSoldier, gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); + //AllTeamsLookForAll( NO_INTERRUPTS ); + } + + return pSoldier->ubID; +} + +void InitiateEnemyAirDropSoldiers( INT32 sGridNo ) +{ + gfTacticalDoHeliRun = TRUE; + + ResetHeliSeats( ); + + //SetHelicopterDroppoint( gMapInformation.sCenterGridNo ); + SetHelicopterDroppoint( sGridNo ); + + SetHelicopterDropDirection( WEST ); + + for ( int i = 0; i < 6; ++i ) + { + UINT8 id = SpawnAirDropElite( gMapInformation.sSouthGridNo + i ); + + if ( id == NOBODY ) + return; + + AddMercToHeli( id ); + } +} + + +void HandleEnemyAirdrop( ) +{ + UINT8 ubScriptCode; + UINT32 uiClock; + INT32 iVol; + ANITILE_PARAMS AniParams; + + if ( gfHandleHeli ) + { + if ( gCurrentUIMode != LOCKUI_MODE ) + { + guiPendingOverrideEvent = LU_BEGINUILOCK; + } + + gfIgnoreScrolling = TRUE; + + uiClock = GetJA2Clock( ); + + if ( (uiClock - guiHeliLastUpdate) > ME_SCRIPT_DELAY ) + { + guiHeliLastUpdate = uiClock; + + if ( fFadingHeliIn ) + { + if ( uiSoundSample != NO_SAMPLE ) + { + iVol = SoundGetVolume( uiSoundSample ); + iVol = __min( HIGHVOLUME, iVol + 5 ); + SoundSetVolume( uiSoundSample, iVol ); + if ( iVol == HIGHVOLUME ) + fFadingHeliIn = FALSE; + } + else + { + fFadingHeliIn = FALSE; + } + } + else if ( fFadingHeliOut ) + { + if ( uiSoundSample != NO_SAMPLE ) + { + iVol = SoundGetVolume( uiSoundSample ); + + iVol = __max( 0, iVol - 5 ); + + SoundSetVolume( uiSoundSample, iVol ); + if ( iVol == 0 ) + { + // Stop sound + SoundStop( uiSoundSample ); + fFadingHeliOut = FALSE; + gfHandleHeli = FALSE; + gfIgnoreScrolling = FALSE; + gbNumHeliSeatsOccupied = 0; + guiPendingOverrideEvent = LU_ENDUILOCK; + UnLockPauseState( ); + UnPauseGame( ); + + RebuildCurrentSquad( ); + + HandleFirstHeliDropOfGame( ); + } + } + else + { + fFadingHeliOut = FALSE; + gfHandleHeli = FALSE; + gfIgnoreScrolling = FALSE; + gbNumHeliSeatsOccupied = 0; + guiPendingOverrideEvent = LU_ENDUILOCK; + UnLockPauseState( ); + UnPauseGame( ); + + RebuildCurrentSquad( ); + + HandleFirstHeliDropOfGame( ); + } + } + + if ( gsHeliScript == MAX_HELI_SCRIPT ) + { + return; + } + + ubScriptCode = ubHeliScripts[gubHeliState][gsHeliScript]; + + // Switch on mode... + if ( gubHeliState == HELI_DROP ) + { + if ( !gfIngagedInDrop ) + { + INT8 bEndVal = (gbHeliRound * NUM_PER_HELI_RUN); + + if ( bEndVal > gbNumHeliSeatsOccupied ) + { + bEndVal = gbNumHeliSeatsOccupied; + } + + // Flugente: watching mercs rope down one by one gets boring fast. So we allow up to 3 mercs to rope down at the same time + for ( int i = 0; i < 3; ++i ) + { + // OK, Check if we have anybody left to send! + if ( gbCurDrop < bEndVal ) + { + // Flugente: it is now possible to use airdrops with soldiers after they have arrived in Arulco. In that case, they might have an animation that breaks EVENT_InitNewSoldierAnim prematurely. + // In the worst case, this can cause the game to be unable to finish the airdrop. For that reason, we set all those soldiers to the STANDING aniamtion. + //MercPtrs[ gusHeliSeats[ gbCurDrop ] ]->usAnimState = STANDING; + MercPtrs[gusHeliSeats[gbCurDrop]]->EVENT_InitNewSoldierAnim( HELIDROP, 0, FALSE ); + + // Change insertion code + MercPtrs[gusHeliSeats[gbCurDrop]]->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO; + MercPtrs[gusHeliSeats[gbCurDrop]]->usStrategicInsertionData = gsGridNoSweetSpot; + + // HEADROCK HAM 3.5: Externalized! + UpdateMercInSector( MercPtrs[gusHeliSeats[gbCurDrop]], gWorldSectorX, gWorldSectorY, startingZ ); + + // IF the first guy down, set squad! + if ( gfFirstGuyDown ) + { + gfFirstGuyDown = FALSE; + //SetCurrentSquad( MercPtrs[ gusHeliSeats[ gbCurDrop ] ]->bAssignment, TRUE ); + } + ScreenMsg( FONT_MCOLOR_WHITE, MSG_INTERFACE, TacticalStr[MERC_HAS_ARRIVED_STR], MercPtrs[gusHeliSeats[gbCurDrop]]->GetName( ) ); + + ++gbCurDrop; + + gfIngagedInDrop = TRUE; + } + else + { + if ( gbExitCount == 0 ) + { + gbExitCount = 2; + } + else + { + --gbExitCount; + + if ( gbExitCount == 1 ) + { + // Goto leave + gsHeliScript = -1; + gubHeliState = HELI_ENDDROP; + } + } + + break; + } + } + } + } + + switch ( ubScriptCode ) + { + case HELI_REST: + + break; + + case HELI_MOVE_DOWN: + + gdHeliZPos -= 1; + gpHeli->pLevelNode->sRelativeZ = (INT16)gdHeliZPos; + break; + + case HELI_MOVE_UP: + + gdHeliZPos += 1; + gpHeli->pLevelNode->sRelativeZ = (INT16)gdHeliZPos; + break; + + case HELI_MOVESMALL_DOWN: + + gdHeliZPos -= 0.25; + gpHeli->pLevelNode->sRelativeZ = (INT16)gdHeliZPos; + break; + + case HELI_MOVESMALL_UP: + + gdHeliZPos += 0.25; + gpHeli->pLevelNode->sRelativeZ = (INT16)gdHeliZPos; + break; + + + case HELI_MOVEY: + + if ( gHeliEnterDirection == SOUTH ) + { + gpHeli->sRelativeY -= 4; + } + else if ( gHeliEnterDirection == EAST ) + { + gpHeli->sRelativeX -= 4; + gpHeli->sRelativeY -= 1; + } + else if ( gHeliEnterDirection == WEST ) + { + gpHeli->sRelativeX += 4; + gpHeli->sRelativeY += 1; + } + else + { + gpHeli->sRelativeY += 4; + } + break; + + case HELI_MOVELARGERY: + + if ( gHeliEnterDirection == SOUTH ) + { + gpHeli->sRelativeY -= 6; + } + else if ( gHeliEnterDirection == EAST ) + { + gpHeli->sRelativeX -= 6; + gpHeli->sRelativeY -= 1; + } + else if ( gHeliEnterDirection == WEST ) + { + gpHeli->sRelativeX += 6; + gpHeli->sRelativeY += 1; + } + else + { + gpHeli->sRelativeY += 6; + } + break; + + case HELI_GOTO_BEGINDROP: + + gsHeliScript = -1; + gubHeliState = HELI_BEGINDROP; + break; + + case HELI_SHOW_HELI: + + // Start animation + memset( &AniParams, 0, sizeof(ANITILE_PARAMS) ); + AniParams.sGridNo = gsGridNoSweetSpot; + AniParams.ubLevelID = ANI_SHADOW_LEVEL; + AniParams.sDelay = 90; + AniParams.sStartFrame = 0; + AniParams.uiFlags = ANITILE_CACHEDTILE | ANITILE_FORWARD | ANITILE_LOOPING; + AniParams.sX = gsHeliXPos; + AniParams.sY = gsHeliYPos; + AniParams.sZ = (INT16)gdHeliZPos; + + if ( gHeliEnterDirection == SOUTH ) + { + strcpy( AniParams.zCachedFile, "TILECACHE\\HELI_SH_SOUTH.STI" ); + } + else if ( gHeliEnterDirection == EAST ) + { + strcpy( AniParams.zCachedFile, "TILECACHE\\HELI_SH_EAST.STI" ); + } + else if ( gHeliEnterDirection == WEST ) + { + strcpy( AniParams.zCachedFile, "TILECACHE\\HELI_SH_WEST.STI" ); + } + else + { + strcpy( AniParams.zCachedFile, "TILECACHE\\HELI_SH.STI" ); + } + + gpHeli = CreateAnimationTile( &AniParams ); + break; + + case HELI_GOTO_DROP: + + // Goto drop animation + gdHeliZPos -= 0.25; + gpHeli->pLevelNode->sRelativeZ = (INT16)gdHeliZPos; + gsHeliScript = -1; + gubHeliState = HELI_DROP; + break; + + case HELI_GOTO_MOVETO: + + // Goto drop animation + gsHeliScript = -1; + gubHeliState = HELI_MOVETO; + break; + + case HELI_GOTO_MOVEAWAY: + + // Goto drop animation + gsHeliScript = -1; + gubHeliState = HELI_MOVEAWAY; + break; + + case HELI_GOTO_EXIT: + + if ( gbCurDrop < gbNumHeliSeatsOccupied ) + { + // Start another run...... + INT16 sX, sY; + + ConvertGridNoToCenterCellXY( gsGridNoSweetSpot, &sX, &sY ); + + if ( gHeliEnterDirection == SOUTH ) + { + gsHeliXPos = sX - (2 * CELL_X_SIZE); + gsHeliYPos = sY + (10 * CELL_Y_SIZE); + } + else if ( gHeliEnterDirection == EAST ) + { + gsHeliXPos = sX + (10 * CELL_X_SIZE); + gsHeliYPos = sY + (2 * CELL_Y_SIZE); + } + else if ( gHeliEnterDirection == WEST ) + { + gsHeliXPos = sX - (10 * CELL_X_SIZE); + gsHeliYPos = sY - (2 * CELL_Y_SIZE); + } + else + { + gsHeliXPos = sX - (2 * CELL_X_SIZE); + gsHeliYPos = sY - (10 * CELL_Y_SIZE); + } + + gdHeliZPos = 0; + gsHeliScript = 0; + gbExitCount = 0; + gubHeliState = HELI_APPROACH; + ++gbHeliRound; + + // Ahh, but still delete the heli! + DeleteAniTile( gpHeli ); + gpHeli = NULL; + } + else + { + // Goto drop animation + gsHeliScript = -1; + gubHeliState = HELI_EXIT; + + // Delete helicopter image! + DeleteAniTile( gpHeli ); + gpHeli = NULL; + gfIgnoreScrolling = FALSE; + + // Select our first guy + SelectSoldier( gusHeliSeats[0], FALSE, TRUE ); + } + break; + + case HELI_DONE: + + // End + fFadingHeliOut = TRUE; + + // HEADROCK HAM 3.5: Update now, in case the LZ is still in a "RED" airspace sector. This is only + // required if the sector is free of enemies... but still required. Will run immediately after the + // helicopter is gone. + UpdateAirspaceControl( ); + break; + } + + ++gsHeliScript; + } + } +} diff --git a/Tactical/Overhead Types.h b/Tactical/Overhead Types.h index 91ff7142..bf13269e 100644 --- a/Tactical/Overhead Types.h +++ b/Tactical/Overhead Types.h @@ -365,7 +365,7 @@ enum UNNAMED_CIV_GROUP_23, VOLUNTEER_CIV_GROUP, // Flugente: civilians that the player recruited BOUNTYHUNTER_CIV_GROUP, // Flugente: hostile bounty hunters - UNNAMED_CIV_GROUP_26, + DOWNEDPILOT_CIV_GROUP, // Flugente: downed pilots UNNAMED_CIV_GROUP_27, UNNAMED_CIV_GROUP_28, UNNAMED_CIV_GROUP_29, diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index a135420b..86fd4344 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -6829,7 +6829,8 @@ void RemoveCapturedEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) // officers and generals are 'special' prisoners... if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP ) ++sNumPrisoner[PRISONER_GENERAL]; - else if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER ) + // downed pilots count as officers too, even though they are civilians. This makes capturing them more rewarding + else if ( (pTeamSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER) || pTeamSoldier->ubCivilianGroup == DOWNEDPILOT_CIV_GROUP ) ++sNumPrisoner[PRISONER_OFFICER]; else if ( pTeamSoldier->bTeam == ENEMY_TEAM ) { diff --git a/Tactical/Points.cpp b/Tactical/Points.cpp index 5aa54137..29008442 100644 --- a/Tactical/Points.cpp +++ b/Tactical/Points.cpp @@ -2826,28 +2826,27 @@ BOOLEAN EnoughAmmo( SOLDIERTYPE *pSoldier, BOOLEAN fDisplay, INT8 bInvPos ) } - void DeductAmmo( SOLDIERTYPE *pSoldier, INT8 bInvPos ) { - OBJECTTYPE * pObj; + return DeductAmmo( pSoldier, &(pSoldier->inv[bInvPos]) ); +} - // tanks never run out of MG ammo! - // unlimited cannon ammo is handled in AI - if ( TANK( pSoldier ) && !Item[pSoldier->inv[bInvPos].usItem].cannon ) +void DeductAmmo( SOLDIERTYPE *pSoldier, OBJECTTYPE* pObj ) +{ + if ( pSoldier && pObj->exists( ) ) { - return; - } + // tanks never run out of MG ammo! + // unlimited cannon ammo is handled in AI + if ( TANK( pSoldier ) && !Item[pObj->usItem].cannon ) + return; - pObj = &(pSoldier->inv[ bInvPos ]); - if ( pObj->exists() == true ) - { if ( Item[pObj->usItem].cannon ) { } else if ( Item[ pObj->usItem ].usItemClass == IC_GUN && !Item[pObj->usItem].cannon && pSoldier->bWeaponMode != WM_ATTACHED_GL && pSoldier->bWeaponMode != WM_ATTACHED_GL_BURST && pSoldier->bWeaponMode != WM_ATTACHED_GL_AUTO ) { // Flugente: check for underbarrel weapons and use that object if necessary - OBJECTTYPE* pObjUsed = pSoldier->GetUsedWeapon( &(pSoldier->inv[bInvPos]) ); + OBJECTTYPE* pObjUsed = pSoldier->GetUsedWeapon( pObj ); // Flugente: external feeding allows us to take ammo from somewhere other than our magazine, like a belt in our inventory our even another mercs if ( gGameExternalOptions.ubExternalFeeding > 0 ) @@ -2921,7 +2920,6 @@ void DeductAmmo( SOLDIERTYPE *pSoldier, INT8 bInvPos ) // Dirty Bars DirtyMercPanelInterface( pSoldier, DIRTYLEVEL1 ); - } } diff --git a/Tactical/Points.h b/Tactical/Points.h index 05fd43e6..1c57fd35 100644 --- a/Tactical/Points.h +++ b/Tactical/Points.h @@ -300,7 +300,7 @@ UINT16 CalculateRaiseGunCost(SOLDIERTYPE *pSoldier, BOOLEAN fAddingRaiseGunCost, INT16 MinAPsToShootOrStab(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 bAimTime, UINT8 ubAddTurningCost, UINT8 ubForceRaiseGunCost = 0 ); BOOLEAN EnoughAmmo( SOLDIERTYPE *pSoldier, BOOLEAN fDisplay, INT8 bInvPos ); void DeductAmmo( SOLDIERTYPE *pSoldier, INT8 bInvPos ); - +void DeductAmmo( SOLDIERTYPE *pSoldier, OBJECTTYPE* pObj ); UINT16 GetAPsToPickupItem( SOLDIERTYPE *pSoldier, INT32 usMapPos ); INT16 MinAPsToPunch(SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 ubAddTurningCost ); diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index a88acf61..c07e96ff 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -391,7 +391,7 @@ enum #define SOLDIER_ENEMY_OBSERVEDTHISTURN 0x08000000 //134217728 // enemy soldier was seen by the player this turn #define SOLDIER_VIP 0x10000000 //268435456 // soldier is a VIP - the player will likely try to assassinate him -#define SOLDIER_BODYGUARD 0x20000000 //536870912 // soldier is a bodyguard for a VIP/* +#define SOLDIER_BODYGUARD 0x20000000 //536870912 // soldier is a bodyguard for a VIP #define SOLDIER_COVERT_TEMPORARY_OVERT 0x40000000 //1073741824 // we are covert, but just performed a obviously suspicious task. For a short time, we can be uncovered more easily #define SOLDIER_MOVEITEM_RESTRICTED 0x80000000 //2147483648 // when moving item, this soldier will not pick up equipment the militia might use // ---------------------------------------------------------------- diff --git a/Tactical/Soldier Create.cpp b/Tactical/Soldier Create.cpp index d114beaa..fe3a613f 100644 --- a/Tactical/Soldier Create.cpp +++ b/Tactical/Soldier Create.cpp @@ -3609,6 +3609,81 @@ void CreatePrisonerOfWar() } } +// create a downed pilot in a random location in the current sector +void CreateDownedPilot( ) +{ + UINT8 tries = 0; + INT32 sGridNo = NOWHERE; + do + { + if ( ++tries > 20 ) + return; + + sGridNo = RandomGridNo( ); + } + // a valid starting gridno must be valid, not in a structure, not in water, and not too near to our mercs + while ( TileIsOutOfBounds( sGridNo ) || FindStructure( sGridNo, STRUCTURE_BLOCKSMOVES ) || TERRAIN_IS_WATER( gpWorldLevelData[sGridNo].ubTerrainID ) || GridNoNearPlayerMercs( sGridNo, 25 ) ); + + SOLDIERCREATE_STRUCT MercCreateStruct; + UINT8 ubID; + + MercCreateStruct.initialize( ); + MercCreateStruct.bTeam = CIV_TEAM; + MercCreateStruct.ubProfile = NO_PROFILE; + MercCreateStruct.sSectorX = gWorldSectorX; + MercCreateStruct.sSectorY = gWorldSectorY; + MercCreateStruct.bSectorZ = gbWorldSectorZ; + MercCreateStruct.sInsertionGridNo = sGridNo; + MercCreateStruct.ubDirection = Random( NUM_WORLD_DIRECTIONS ); + MercCreateStruct.ubBodyType = Random( ADULTFEMALEMONSTER ); + + RandomizeNewSoldierStats( &MercCreateStruct ); + + SOLDIERTYPE* pSoldier = TacticalCreateSoldier( &MercCreateStruct, &ubID ); + + if ( pSoldier ) + { + // a downed pilot might be wounded + pSoldier->stats.bLife = min( pSoldier->stats.bLifeMax, max( OKLIFE + 20, pSoldier->stats.bLife - 20 ) ); + pSoldier->bBleeding = pSoldier->stats.bLifeMax - pSoldier->stats.bLife; + + AddSoldierToSector( pSoldier->ubID ); + + // set correct civ group + pSoldier->ubCivilianGroup = DOWNEDPILOT_CIV_GROUP; + + // make him wear administrator uniform + UINT16 usPaletteAnimSurface = LoadSoldierAnimationSurface( pSoldier, pSoldier->usAnimState ); + + if ( usPaletteAnimSurface != INVALID_ANIMATION_SURFACE ) + { + SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[UNIFORM_ENEMY_ADMIN].vest ); + SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[UNIFORM_ENEMY_ADMIN].pants ); + + // Use palette from HVOBJECT, then use substitution for pants, etc + memcpy( pSoldier->p8BPPPalette, gAnimSurfaceDatabase[usPaletteAnimSurface].hVideoObject->pPaletteEntry, sizeof(pSoldier->p8BPPPalette) * 256 ); + + SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->HeadPal ); + SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->VestPal ); + SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->PantsPal ); + SetPaletteReplacement( pSoldier->p8BPPPalette, pSoldier->SkinPal ); + + pSoldier->CreateSoldierPalettes( ); + + // Dirty + fInterfacePanelDirty = DIRTYLEVEL2; + } + + // So we can see them! + AllTeamsLookForAll( NO_INTERRUPTS ); + + // downed pilots are hostile, even though they stand no chance against us + gTacticalStatus.fCivGroupHostile[POW_PRISON_CIV_GROUP] = CIV_GROUP_HOSTILE; + + pSoldier->aiData.bNeutral = FALSE; + } +} + void RandomizeRelativeLevel( INT8 *pbRelLevel, UINT8 ubSoldierClass ) { UINT8 ubLocationModifier = 0; diff --git a/Tactical/Soldier Create.h b/Tactical/Soldier Create.h index e5555d2d..999142d1 100644 --- a/Tactical/Soldier Create.h +++ b/Tactical/Soldier Create.h @@ -457,6 +457,9 @@ void CreateAssassin(UINT8 disguisetype); // create a prisoner (in a prison cell) in the current sector void CreatePrisonerOfWar(); +// create a downed pilot in a random location in the current sector +void CreateDownedPilot(); + // randomly generates a relative level rating (attributes or equipment) void RandomizeRelativeLevel( INT8 *pbRelLevel, UINT8 ubSoldierClass ); diff --git a/Tactical/Soldier Init List.cpp b/Tactical/Soldier Init List.cpp index ea3913f7..0cd429e6 100644 --- a/Tactical/Soldier Init List.cpp +++ b/Tactical/Soldier Init List.cpp @@ -954,6 +954,8 @@ UINT8 AddSoldierInitListTeamToWorld( INT8 bTeam, UINT8 ubMaxNum ) { SectorAddPrisonersofWar(gWorldSectorX, gWorldSectorY, gbWorldSectorZ); + SectorAddDownedPilot( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); + LuaHandleSectorTacticalEntry( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); } @@ -1009,6 +1011,8 @@ UINT8 AddSoldierInitListTeamToWorld( INT8 bTeam, UINT8 ubMaxNum ) { SectorAddPrisonersofWar(gWorldSectorX, gWorldSectorY, gbWorldSectorZ); + SectorAddDownedPilot( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); + LuaHandleSectorTacticalEntry( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); } @@ -3049,3 +3053,28 @@ void SectorAddPrisonersofWar( INT16 sMapX, INT16 sMapY, INT16 sMapZ ) break; } } + +// Flugente: decide wether to add a downed pilot if a helicopter was shot down here +void SectorAddDownedPilot( INT16 sMapX, INT16 sMapY, INT16 sMapZ ) +{ + // not in underground sectors + if ( sMapZ > 0 ) + return; + + // get sector + SECTORINFO *pSector = &SectorInfo[SECTOR( sMapX, sMapY )]; + if ( !pSector ) + return; + + if ( pSector->usSectorInfoFlag & SECTORINFO_ENEMYHELI_SHOTDOWN ) + { + // it is likely that a pilot has survived, but not guaranteed + if ( Chance( 75 ) ) + { + CreateDownedPilot(); + } + + // remove the flag. We can only find the pilot the first time we visit this sector after the heli was shut down + pSector->usSectorInfoFlag &= ~SECTORINFO_ENEMYHELI_SHOTDOWN; + } +} \ No newline at end of file diff --git a/Tactical/Soldier Init List.h b/Tactical/Soldier Init List.h index 85051259..5715500e 100644 --- a/Tactical/Soldier Init List.h +++ b/Tactical/Soldier Init List.h @@ -65,7 +65,7 @@ void SectorAddAssassins( INT16 sMapX, INT16 sMapY, INT16 sMapZ ); // Flugente: decide wether to create prisoners of war in a sector. Not to be confused with player POWs void SectorAddPrisonersofWar( INT16 sMapX, INT16 sMapY, INT16 sMapZ ); -// Flugente: add script-defined civilians if Lua functions say so -void SectorAddLuaCivilians( INT16 sMapX, INT16 sMapY, INT16 sMapZ ); +// Flugente: decide wether to add a downed pilot if a helicopter was shot down here +void SectorAddDownedPilot( INT16 sMapX, INT16 sMapY, INT16 sMapZ ); #endif \ No newline at end of file diff --git a/Tactical/XML_Background.cpp b/Tactical/XML_Background.cpp index d213a0f4..18f89ae9 100644 --- a/Tactical/XML_Background.cpp +++ b/Tactical/XML_Background.cpp @@ -117,6 +117,7 @@ backgroundStartElementHandle(void *userData, const XML_Char *name, const XML_Cha strcmp(name, "hearing_night") == 0 || strcmp(name, "hearing_day") == 0 || strcmp(name, "disarm_trap") == 0 || + strcmp(name, "SAM_cth" ) == 0 || strcmp(name, "approach_friendly") == 0 || strcmp(name, "approach_direct") == 0 || strcmp(name, "approach_threaten") == 0 || @@ -486,6 +487,11 @@ backgroundEndElementHandle(void *userData, const XML_Char *name) pData->curElement = ELEMENT; pData->curBackground.value[BG_PERC_DISARM] = min( 50, max( -50, (INT16)atol( pData->szCharData ) ) ); } + else if ( strcmp( name, "SAM_cth" ) == 0 ) + { + pData->curElement = ELEMENT; + pData->curBackground.value[BG_PERC_SAM_CTH] = min( 100, max( -50, (INT16)atol( pData->szCharData ) ) ); + } else if(strcmp(name, "approach_friendly") == 0) { pData->curElement = ELEMENT; diff --git a/Tactical/merc entering.h b/Tactical/merc entering.h index c4da88b2..bb98e971 100644 --- a/Tactical/merc entering.h +++ b/Tactical/merc entering.h @@ -1,6 +1,7 @@ #ifndef _MERC_ENTRING_H #define _MERC_ENTRING_H +extern BOOLEAN gfIngagedInDrop; void ResetHeliSeats( ); void AddMercToHeli( UINT8 ubID ); @@ -11,9 +12,12 @@ void SetHelicopterDropDirection( UINT8 usDirection ); void StartHelicopterRun(); -void HandleHeliDrop( ); +void HandleHeliDrop( BOOLEAN fPlayer = TRUE ); -extern BOOLEAN gfIngagedInDrop; +UINT16 SpawnAirDropElite( INT32 sGridNo ); + +void InitiateEnemyAirDropSoldiers( INT32 sGridNo ); +void HandleEnemyAirdrop( ); #endif diff --git a/TileEngine/structure.cpp b/TileEngine/structure.cpp index e4942ec2..11ff0494 100644 --- a/TileEngine/structure.cpp +++ b/TileEngine/structure.cpp @@ -48,6 +48,7 @@ #include "Soldier Functions.h" #include "Animation Control.h" #include "Soldier Ani.h" + #include "ASD.h" // added by Flugente #endif #ifdef COUNT_PATHS @@ -1930,11 +1931,15 @@ BOOLEAN DamageStructure( STRUCTURE * pStructure, UINT8 ubDamage, UINT8 ubReason, CHECKF( pBase ); if (pBase->ubHitPoints <= ubDamage) { + UpdateAndDamageEnemyHeliIfFound( gWorldSectorX, gWorldSectorY, gbWorldSectorZ, sGridNo, ubDamage, TRUE ); + // boom! structure destroyed! return( TRUE ); } else { + UpdateAndDamageEnemyHeliIfFound( gWorldSectorX, gWorldSectorY, gbWorldSectorZ, sGridNo, ubDamage, FALSE ); + pBase->ubHitPoints -= ubDamage; //Since the structure is being damaged, set the map element that a structure is damaged diff --git a/Utils/Text.h b/Utils/Text.h index 6c69e74f..f4f17a47 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -2925,6 +2925,9 @@ extern STR16 szIMPGearDropDownNoneText[]; // Flugente: militia movement extern STR16 szMilitiaStrategicMovementText[]; +// Flugente: enemy heli/SAM +extern STR16 szEnemyHeliText[]; + #define TACTICAL_INVENTORY_DIALOG_NUM 16 #define TACTICAL_COVER_DIALOG_NUM 16 diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 78078e0c..c95ea3c7 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -4155,6 +4155,7 @@ STR16 pMapPopUpInventoryText[] = { L"存货", L"离开", + L"Repair", // TODO.Translate }; // town strings @@ -4523,6 +4524,7 @@ STR16 pTransactionText[] = L"释放俘虏所需的赎金", //L"Ransom for released prisoners", L"记录捐款费", //L"WHO data subscription", // Flugente: disease L"Kerberus安保公司的费用", //L"Payment to Kerberus",  // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair // TODO.Translate }; STR16 pTransactionAlternateText[] = @@ -8898,6 +8900,7 @@ STR16 szBackgroundText_Value[]= L" %s%d夜晚听力范围\n", //L" %s%d hearing range during the night\n", L" %s%d白天听力范围\n", //L" %s%d hearing range during the day\n", L" %s%d解除陷阱效率\n", + L" %s%d%% CTH with SAMs\n", // TODO.Translate L" %s%d%%友好对话效果\n", //L" %s%d%% effectiveness to friendly approach\n", L" %s%d%%直接对话效果\n", //L" %s%d%% effectiveness to direct approach\n", @@ -10846,6 +10849,21 @@ STR16 szMilitiaStrategicMovementText[] = L"民兵的志愿者: %d (+%5.3f)", //L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = // TODO.Translate +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + // WANNE: Some Chinese specific strings that needs to be in unicode! STR16 ChineseSpecString1 = L"%%"; //defined in _ChineseText.cpp as this file is already unicode STR16 ChineseSpecString2 = L"*%3d%%%%"; //defined in _ChineseText.cpp as this file is already unicode diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 859c56ae..d9de9662 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -4154,6 +4154,7 @@ STR16 pMapPopUpInventoryText[] = { L"Inventaris", L"OK", + L"Repair", // TODO.Translate }; // town strings @@ -4524,6 +4525,7 @@ STR16 pTransactionText[] = L"Ransom for released prisoners", // Flugente: prisoner system TODO.Translate L"WHO data subscription", // Flugente: disease TODO.Translate L"Payment to Kerberus", // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair // TODO.Translate }; STR16 pTransactionAlternateText[] = @@ -8912,6 +8914,7 @@ STR16 szBackgroundText_Value[]= L" %s%d hearing range during the night\n", L" %s%d hearing range during the day\n", L" %s%d effectivity at disarming traps\n", // TODO.Translate + L" %s%d%% CTH with SAMs\n", // TODO.Translate L" %s%d%% effectiveness to friendly approach\n", L" %s%d%% effectiveness to direct approach\n", @@ -10860,4 +10863,19 @@ STR16 szMilitiaStrategicMovementText[] = L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = // TODO.Translate +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + #endif //DUTCH diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index ee96469a..f96c1199 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -4152,6 +4152,7 @@ STR16 pMapPopUpInventoryText[] = { L"Inventory", L"Exit", + L"Repair", }; // town strings @@ -4520,6 +4521,7 @@ STR16 pTransactionText[] = L"Ransom for released prisoners", // Flugente: prisoner system L"WHO data subscription", // Flugente: disease L"Payment to Kerberus", // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair }; STR16 pTransactionAlternateText[] = @@ -8896,6 +8898,7 @@ STR16 szBackgroundText_Value[]= L" %s%d hearing range during the night\n", L" %s%d hearing range during the day\n", L" %s%d effectivity at disarming traps\n", + L" %s%d%% CTH with SAMs\n", L" %s%d%% effectiveness to friendly approach\n", L" %s%d%% effectiveness to direct approach\n", @@ -10898,4 +10901,19 @@ STR16 szMilitiaStrategicMovementText[] = L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + #endif //ENGLISH diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index e49a2332..9ee99816 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -4158,6 +4158,7 @@ STR16 pMapPopUpInventoryText[] = { L"Inventaire", L"Quitter", + L"Repair", // TODO.Translate }; // town strings @@ -4528,6 +4529,7 @@ STR16 pTransactionText[] = L"Argent des prisonniers libérés", // Flugente: prisoner system L"WHO data subscription", // Flugente: disease TODO.Translate L"Payment to Kerberus", // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair // TODO.Translate }; STR16 pTransactionAlternateText[] = @@ -8897,6 +8899,7 @@ STR16 szBackgroundText_Value[]= L" %s%d en audition pendant la nuit\n", L" %s%d en audition pendant la journée\n", L" %s%d d'efficacité à désamorcer les pièges\n", + L" %s%d%% CTH with SAMs\n", // TODO.Translate L" %s%d%% d'efficacité dans une approche amicale\n", L" %s%d%% d'efficacité dans une approche directe\n", @@ -10845,4 +10848,19 @@ STR16 szMilitiaStrategicMovementText[] = L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = // TODO.Translate +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + #endif //FRENCH diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index e5bfb824..c9371cb6 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -4153,6 +4153,7 @@ STR16 pMapPopUpInventoryText[] = { L"Inventar", L"Exit", + L"Repair", // TODO.Translate }; // town strings @@ -4500,6 +4501,7 @@ STR16 pTransactionText[] = L"Lösegeld erpresst", // Flugente: prisoner system L"WHO data subscription", // Flugente: disease TODO.Translate L"Payment to Kerberus", // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair // TODO.Translate }; STR16 pTransactionAlternateText[] = @@ -8728,6 +8730,7 @@ STR16 szBackgroundText_Value[]= L" %s%d hearing range during the night\n", L" %s%d hearing range during the day\n", L" %s%d effectivity at disarming traps\n", // TODO.Translate + L" %s%d%% CTH with SAMs\n", // TODO.Translate L" %s%d%% effectiveness to friendly approach\n", L" %s%d%% effectiveness to direct approach\n", @@ -10676,4 +10679,19 @@ STR16 szMilitiaStrategicMovementText[] = L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = // TODO.Translate +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + #endif //GERMAN diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index cc2fc967..dbf34937 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -4148,6 +4148,7 @@ STR16 pMapPopUpInventoryText[] = { L"Inventario", L"Uscita", + L"Repair", // TODO.Translate }; // town strings @@ -4518,6 +4519,7 @@ STR16 pTransactionText[] = L"Ransom for released prisoners", // Flugente: prisoner system TODO.Translate L"WHO data subscription", // Flugente: disease TODO.Translate L"Payment to Kerberus", // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair // TODO.Translate }; STR16 pTransactionAlternateText[] = @@ -8906,6 +8908,7 @@ STR16 szBackgroundText_Value[]= L" %s%d hearing range during the night\n", L" %s%d hearing range during the day\n", L" %s%d effectivity at disarming traps\n", // TODO.Translate + L" %s%d%% CTH with SAMs\n", // TODO.Translate L" %s%d%% effectiveness to friendly approach\n", L" %s%d%% effectiveness to direct approach\n", @@ -10854,4 +10857,19 @@ STR16 szMilitiaStrategicMovementText[] = L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = // TODO.Translate +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + #endif //ITALIAN diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index 186c5bd0..f95acaae 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -4159,6 +4159,7 @@ STR16 pMapPopUpInventoryText[] = { L"Inwentarz", L"Zamknij", + L"Repair", // TODO.Translate }; // town strings @@ -4529,6 +4530,7 @@ STR16 pTransactionText[] = L"Ransom for released prisoners", // Flugente: prisoner system TODO.Translate L"WHO data subscription", // Flugente: disease TODO.Translate L"Payment to Kerberus", // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair // TODO.Translate }; STR16 pTransactionAlternateText[] = @@ -8919,6 +8921,7 @@ STR16 szBackgroundText_Value[]= L" %s%d hearing range during the night\n", L" %s%d hearing range during the day\n", L" %s%d effectivity at disarming traps\n", // TODO.Translate + L" %s%d%% CTH with SAMs\n", // TODO.Translate L" %s%d%% effectiveness to friendly approach\n", L" %s%d%% effectiveness to direct approach\n", @@ -10867,4 +10870,19 @@ STR16 szMilitiaStrategicMovementText[] = L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = // TODO.Translate +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + #endif //POLISH diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index 1e0f2387..cf75d80e 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -4154,6 +4154,7 @@ STR16 pMapPopUpInventoryText[] = { L"Инвентарь", L"Выйти", + L"Repair", // TODO.Translate }; // town strings @@ -4522,6 +4523,7 @@ STR16 pTransactionText[] = L"Выкуп за освобожденных заключенных", // Flugente: prisoner system L"ВОЗ, подписка", // Flugente: disease L"Оплата услуг Цербер", // Flugente: PMC + L"SAM site repair", // Flugente: SAM repair // TODO.Translate }; STR16 pTransactionAlternateText[] = @@ -8896,6 +8898,7 @@ STR16 szBackgroundText_Value[]= L" %s%d дальность слуха ночью\n", L" %s%d дальность слуха днем\n", L" %s%d эффективность при разминировании ловушек\n", + L" %s%d%% CTH with SAMs\n", // TODO.Translate L" %s%d%% эффективность дружеского обращения\n", L" %s%d%% эффективность прямого обращении\n", @@ -10898,4 +10901,19 @@ STR16 szMilitiaStrategicMovementText[] = L"Militia Volunteers: %d (+%5.3f)", }; +STR16 szEnemyHeliText[] = // TODO.Translate +{ + L"Enemy helicopter shot down in %s!", + L"We... uhm... currently don't control that site, commander...", + L"The SAM does not need maintenance at the moment.", + L"We've already ordered the repair, this will take time.", + + L"We do not have enough resources to do that.", + L"Repair SAM site? This will cost %d$ and take %d hours.", + L"Enemy helicopter hit in %s.", + L"%s fires %s at enemy helicopter in %s.", + + L"SAM in %s fires at enemy helicopter in %s.", +}; + #endif //RUSSIAN diff --git a/gamescreen.cpp b/gamescreen.cpp index 91bdc06d..a6a05c8a 100644 --- a/gamescreen.cpp +++ b/gamescreen.cpp @@ -640,7 +640,7 @@ UINT32 MainGameScreenHandle(void) // Start heli Run... StartHelicopterRun(); - + // Update clock by one so that our DidGameJustStatrt() returns now false for things like LAPTOP, etc... SetGameTimeCompressionLevel( TIME_COMPRESS_X1 );