/** * @file * @author Flugente (bears-pit.com) */ #ifdef PRECOMPILEDHEADERS #include "Laptop All.h" #else #include #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 BOOLEAN gfTacticalDoHeliRun; // 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_KNOWNBYINTEL 0x00000004 // the player knows about this heli from intel (it will be displayed on the map) #define ENEMYHELI_KNOWNTOPLAYER 0x00000008 // the player knows about this heli (it will 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] = 200000; gASDResource[ASD_FUEL] = 0; gASDResource[ASD_HELI] = 0; gASDResource[ASD_JEEP] = 2; gASDResource[ASD_TANK] = 2; gASDResource[ASD_ROBOT] = 8; 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; gASDResource_Ordered[ASD_ROBOT] = 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; // enemy helis are unlocked either by a cutscene OR if a minimum progress threshold has been reached if ( !(gASD_Flags & ASDFACT_HELI_UNLOCKED) && HighestPlayerProgressPercentage( ) >= gGameExternalOptions.usEnemyHeliMinimumProgress ) SetASDFlag( ASDFACT_HELI_UNLOCKED ); if ( !(gASD_Flags & ASDFACT_JEEP_UNLOCKED) && HighestPlayerProgressPercentage( ) >= gGameExternalOptions.usJeepMinimumProgress ) SetASDFlag( ASDFACT_JEEP_UNLOCKED ); if ( !(gASD_Flags & ASDFACT_TANK_UNLOCKED) && HighestPlayerProgressPercentage( ) >= gGameExternalOptions.usTankMinimumProgress ) SetASDFlag( ASDFACT_TANK_UNLOCKED ); if ( !(gASD_Flags & ASDFACT_ROBOT_UNLOCKED) && HighestPlayerProgressPercentage( ) >= gGameExternalOptions.usRobotMinimumProgress ) SetASDFlag( ASDFACT_ROBOT_UNLOCKED ); // determine whether we need to buy new toys ASDDecideOnPurchases(); // decide how to use helicopters ASDDecideHeliOperations(); } void ASDDecideOnPurchases() { INT32 needed_fuel = 0; INT32 needed_heli = 0; INT32 needed_tank = 0; INT32 needed_jeep = 0; INT32 needed_robot = 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 no longer want jeeps - they are replaced by tanks if ( (gASD_Flags & ASDFACT_TANK_UNLOCKED) && gGameExternalOptions.fASDAssignsTanks ) { needed_jeep = 0; } // 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 ); } // do we need new robots? if ( gASD_Flags & ASDFACT_ROBOT_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_robot = min( 10, 2 + highestplayerprogress / 10 ); // how many are needed? needed_robot = max( 0, needed_robot - gASDResource[ASD_ROBOT] ); needed_fuel += needed_robot * ASDResourceCostFuel( ASD_ROBOT ); } // how much new fuel do we need? needed_fuel = max( 0, needed_fuel - gASDResource[ASD_FUEL] ); // reduce order by what we'e already 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] ); needed_robot = max( 0, needed_robot - gASDResource_Ordered[ASD_ROBOT] ); // 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 ) + needed_robot * ASDResourceCostMoney( ASD_ROBOT ) > gASDResource[ASD_MONEY] ) { if ( needed_tank ) --needed_tank; else if ( needed_heli ) --needed_heli; else if ( needed_jeep ) --needed_jeep; else if ( needed_robot ) --needed_robot; 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 ); } if ( needed_robot ) { AddStrategicAIResources( ASD_MONEY, -needed_robot * ASDResourceCostMoney( ASD_ROBOT ) ); gASDResource_Ordered[ASD_ROBOT] += needed_robot; AddStrategicEvent( EVENT_ASD_PURCHASE_ROBOT, GetWorldTotalMin( ) + ASDResourceDeliveryTime( ASD_ROBOT ), needed_robot ); } } 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; // if we dont have enough soldiers left to staff a helicopter, don't send one out if ( giReinforcementPool < gEnemyHeliMaxTroops ) 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_Y - 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 += 30; 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 = NumPlayerTeamMembersInSector( sX, sY, 0); if ( num_militia + num_mercs ) { // mercs are much more dangerous than militia, so increase their weight if ( num_militia + 2.5f * num_mercs > 2 * gEnemyHeliMaxTroops ) rating = 0; else if ( num_militia + 2.5f * 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 + 2.5f * 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; // sevenfm: don't change reinforcement pool when unlimited reinforcements enabled if (!gfUnlimitedTroops) 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 ASDReduceOrderedStrategicResources( UINT8 aType, INT32 aAmount ) { gASDResource_Ordered[aType] = max( 0, gASDResource_Ordered[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( ) { hp = 0; flagmask |= ENEMYHELI_DESTROYED; // if the player knows about this heli, play a sound and leave a message if ( flagmask & (ENEMYHELI_KNOWNBYINTEL|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, there might be survivors or wreckage if ( flagmask & ENEMYHELI_SHOTDOWN ) { // remember that a heli crashed here. When entering the sector the next time, we might spawn a downed pilot or wreckage SectorInfo[sector_current].usSectorInfoFlag |= SECTORINFO_ENEMYHELI_SHOTDOWN; // if the heli had soldiers aboard, they might have survived the crash. Add them to the sector in that case // don't do this in sectors that are impassable if ( troopcount && !SectorIsImpassable( sector_current ) ) { // it is possible that we shoot down a helicopter while in a fight. In this case, adding soldiers to a sector would either cause a second battle or a state where a fight should start but doesn't // both is bad, so we simply don't add soldiers in that case. The player will hardly notice, as there is only a chance that soldiers survive anyway if ( !gTacticalStatus.fEnemyInSector && !(gTacticalStatus.uiFlags & INCOMBAT) ) { // chance that they survived at all if ( Chance( 75 ) ) { // not all might have made it SectorInfo[sector_current].ubNumElites = min( 255, SectorInfo[sector_current].ubNumElites + 1 + Random(troopcount) ); // if no militia or mercs are here, enemy takes this sector if ( NumPlayerTeamMembersInSector( SECTORX( sector_current ), SECTORY( sector_current ), 0 ) + NumNonPlayerTeamMembersInSector( SECTORX( sector_current ), SECTORY( sector_current ), MILITIA_TEAM ) <= 0 ) { SetThisSectorAsEnemyControlled( SECTORX( sector_current ), SECTORY( sector_current ), 0, TRUE ); } CheckCombatInSectorDueToUnusualEnemyArrival( ENEMY_TEAM, SECTORX( sector_current ), SECTORY( sector_current ), 0 ); } } } } // reduce these at the end, as we might do sth based on them fuel = 0; troopcount = 0; } 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 = SECTOR( gModSettings.usEnemyHeliBaseSectorX, gModSettings.usEnemyHeliBaseSectorY ); (*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 = SECTOR( gModSettings.usEnemyHeliBaseSectorX, gModSettings.usEnemyHeliBaseSectorY ); 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_KNOWNBYINTEL|ENEMYHELI_KNOWNTOPLAYER)) ) continue; set.insert( (*it).sector_current ); } return set; } std::vector GetEnemyHeliIDKnowledgeStatus() { INT16 id = -1; std::vector vec; std::vector::iterator itend = gEnemyHeliVector.end(); for ( std::vector::iterator it = gEnemyHeliVector.begin(); it != itend; ++it ) { ++id; if ( ( ( *it ).flagmask & ( ENEMYHELI_DESTROYED | ENEMYHELI_KNOWNBYINTEL | ENEMYHELI_KNOWNTOPLAYER ) ) ) vec.push_back( -1 ); else vec.push_back( id ); } return vec; } void BuyHeliInfoWithIntel( 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; heli.flagmask |= ENEMYHELI_KNOWNBYINTEL; } } 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 ( NumPlayerTeamMembersInSector( 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 ( !NumPlayerTeamMembersInSector( 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 ) { for ( int i = 0; i < NUMBER_OF_SAMS; ++i ) { BOOLEAN samworking = FALSE; if ( DoesSamCoverSector( i, heli.sector_current, &samworking ) && samworking ) { StrategicMapElement *pSAMStrategicMap = &(StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX( pSamList[i] )]); // also check whether this sector is not enemy-controlled - we can't have the AI shooting at its own helis if ( pSAMStrategicMap && !pSAMStrategicMap->fEnemyControlled ) { UINT16 ubBestSAMOperatorID = NOBODY; FLOAT samcth = GetBestSAMOperatorCTH_Player( gpSamSectorX[i], gpSamSectorY[i], 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( FLOAT( abs( heli_x - gpSamSectorX[i] ) * abs( heli_x - gpSamSectorX[i] ) + abs( heli_y - gpSamSectorY[i] ) * abs( heli_y - gpSamSectorY[i] ) ) ); // 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[i], gpSamSectorY[i], 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_KNOWNBYINTEL|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(FLOAT(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]; // if we already know about this from intel, nothing to do here if ( heli.flagmask & ENEMYHELI_KNOWNBYINTEL ) return; 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 ( NumPlayerTeamMembersInSector( 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 ) { for ( int i = 0; i < NUMBER_OF_SAMS; ++i ) { if ( DoesSamCoverSector( i, heli.sector_current ) ) { // if mercs man the SAM, they will let us know of the new SAM if ( NumPlayerTeamMembersInSector( gpSamSectorX[i], gpSamSectorY[i], 0 ) ) { // if we previously did not know of the heli, our mercs will alert us if ( !(heli.flagmask & ENEMYHELI_KNOWNTOPLAYER) ) { SayQuoteFromAnyBodyInThisSector( gpSamSectorX[i], gpSamSectorY[i], 0, QUOTE_AIR_RAID ); break; } } } } heli.flagmask |= ENEMYHELI_KNOWNTOPLAYER; return; } // if we haven't exited this function already, nobody notices the heli - delete knowledge heli.flagmask &= ~ENEMYHELI_KNOWNTOPLAYER; } } UINT8 NumPlayerAirSpaceOnHeliPath( UINT8 aStart, UINT8 aEnd ) { UINT8 samcontacts = 0; UINT8 tmpsect = aStart; while ( tmpsect != aEnd ) { tmpsect = GetNextEnemyHeliSector( tmpsect, aEnd ); if ( StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX( tmpsect )].usAirType & AIRSPACE_PLAYER_ACTIVE ) ++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 != SECTOR( gModSettings.usEnemyHeliBaseSectorX, gModSettings.usEnemyHeliBaseSectorY ) ) 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), gModSettings.sEnemyHeliBaseParkGridno[id], gModSettings.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 != SECTOR( gModSettings.usEnemyHeliBaseSectorX, gModSettings.usEnemyHeliBaseSectorY ) ) 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 ( gModSettings.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 ///////////////////////////////////////////////////////// UINT32 ASDResourceCostFuel( UINT8 aType ) { switch ( aType ) { case ASD_TANK: return gGameExternalOptions.gASDResource_Fuel_Tank; break; case ASD_JEEP: return gGameExternalOptions.gASDResource_Fuel_Jeep; break; case ASD_ROBOT: return gGameExternalOptions.gASDResource_Fuel_Robot; break; } return 0; } // if ASD has tanks, it can allow the queen to upgrade soldiers to tanks BOOLEAN ASDSoldierUpgradeToTank( ) { if ( gGameExternalOptions.fASDActive && gGameExternalOptions.fASDAssignsTanks ) { if ( gASD_Flags & ASDFACT_TANK_UNLOCKED ) { if ( gASDResource[ASD_TANK] ) { gASDResource[ASD_TANK] = max( 0, gASDResource[ASD_TANK] - 1 ); return TRUE; } } } return FALSE; } BOOLEAN ASDSoldierUpgradeToJeep( ) { if ( gGameExternalOptions.fASDActive && gGameExternalOptions.fASDAssignsJeeps ) { if ( gASD_Flags & ASDFACT_JEEP_UNLOCKED ) { if ( gASDResource[ASD_JEEP] ) { gASDResource[ASD_JEEP] = max( 0, gASDResource[ASD_JEEP] - 1 ); return TRUE; } } } return FALSE; } BOOLEAN ASDSoldierUpgradeToRobot( ) { if ( gGameExternalOptions.fASDActive && gGameExternalOptions.fASDAssignsRobots ) { if ( gASD_Flags & ASDFACT_ROBOT_UNLOCKED ) { if ( gASDResource[ASD_ROBOT] ) { gASDResource[ASD_ROBOT] = max( 0, gASDResource[ASD_ROBOT] - 1 ); return TRUE; } } } return FALSE; }