From d8e4a294645684edccf1079644170dadc4e74f8a Mon Sep 17 00:00:00 2001 From: Samuel Holgersson Date: Fri, 15 Dec 2023 18:59:25 +0100 Subject: [PATCH] Add AI visibility handling logic and fix some bugs Add mechanism for AI to keep track of what plots other AI players can see. Now instead of cheating by checking what another player can see directly, it will now base its knowledge on known enemy terrory and unit placements. Currently does not handle the case where an enemy unit moves during your turn (retreating). Shouldn't have a big impact overall. Fix AI sometimes not clearing their route caches if they only have one city. Fix AI never building improvements near their own capital if they are at war with a civ with a spy there (part of new visibility mechanism). Possibly fixes AI mistakenly cancelling sneak operations based on bad information. Fix AI never building lower level roads even when it's supposed to. Fix vassals removing their master's roads. Masters will now build routes through vassals' lands when needed. Fix AI being less prone to remove roads when losing gold, rather than the other way round. Minor cleanup regarding road building and removing. Should fix potential Iroquis and Songhai issues. Fix incorrect planned road maintenance cost calculation. AI will no longer build villages on roads that are about to be removed. AI can build routes through minor civs even if they don't have a quest for it. AI will now not avoid building routes through their vassals' lands. Routes between non-capital cities also benefit villages and towns, update pathfinding to take this into account. Up route removal priority a little bit. Remove unused variables. Include Towns in route pathfinding logic. Clean up some route logic, always build profitable roads, even if we are losing gold. Don't build railroads for city state quests. --- CvGameCoreDLL_Expansion2/CvAIOperation.cpp | 2 +- CvGameCoreDLL_Expansion2/CvAStar.cpp | 48 +++--- CvGameCoreDLL_Expansion2/CvAStarNode.h | 6 +- .../CvBuilderTaskingAI.cpp | 131 +++++++-------- CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.h | 7 +- CvGameCoreDLL_Expansion2/CvDangerPlots.cpp | 4 +- CvGameCoreDLL_Expansion2/CvPlayerAI.cpp | 1 + CvGameCoreDLL_Expansion2/CvPlot.cpp | 93 +++++++---- CvGameCoreDLL_Expansion2/CvPlot.h | 8 +- CvGameCoreDLL_Expansion2/CvTacticalAI.cpp | 152 ++++++++++++++++-- CvGameCoreDLL_Expansion2/CvTacticalAI.h | 11 ++ 11 files changed, 328 insertions(+), 135 deletions(-) diff --git a/CvGameCoreDLL_Expansion2/CvAIOperation.cpp b/CvGameCoreDLL_Expansion2/CvAIOperation.cpp index 4e295c58ad..0e335ec62c 100644 --- a/CvGameCoreDLL_Expansion2/CvAIOperation.cpp +++ b/CvGameCoreDLL_Expansion2/CvAIOperation.cpp @@ -1615,7 +1615,7 @@ bool CvAIOperationMilitary::CheckTransitionToNextStage() int nVisible = 0; for (CvUnit* pUnit = pThisArmy->GetFirstUnit(); pUnit; pUnit = pThisArmy->GetNextUnit(pUnit)) { - if (pUnit->plot()->isVisible( GET_PLAYER(m_eEnemy).getTeam() )) + if (GET_PLAYER(pUnit->getOwner()).GetTacticalAI()->IsVisibleToPlayer(pUnit->plot(), GET_PLAYER(m_eEnemy).getTeam())) nVisible++; } diff --git a/CvGameCoreDLL_Expansion2/CvAStar.cpp b/CvGameCoreDLL_Expansion2/CvAStar.cpp index 9ba2979670..cea8d26a04 100644 --- a/CvGameCoreDLL_Expansion2/CvAStar.cpp +++ b/CvGameCoreDLL_Expansion2/CvAStar.cpp @@ -2184,7 +2184,7 @@ int CityConnectionWaterValid(const CvAStarNode* parent, const CvAStarNode* node, // -------------------------------------------------------------------------------- /// Prefer building routes that can have villages. -int BuildRouteVillageBonus(CvPlayer* pPlayer, CvPlot* pPlot, RouteTypes eRouteType, CvBuilderTaskingAI* eBuilderTaskingAi) +static int BuildRouteVillageBonus(CvPlayer* pPlayer, CvPlot* pPlot, RouteTypes eRouteType, CvBuilderTaskingAI* eBuilderTaskingAi) { // If we're not the owner of this plot, bail out if (!pPlot->isOwned() || pPlot->getOwner() != pPlayer->GetID()) @@ -2239,9 +2239,6 @@ int BuildRouteVillageBonus(CvPlayer* pPlayer, CvPlot* pPlot, RouteTypes eRouteTy if (pkImprovementInfo == NULL) continue; - if (pkImprovementInfo->IsCreatedByGreatPerson()) - continue; - for (int iYield = 0; iYield < NUM_YIELD_TYPES; iYield++) { YieldTypes eYield = (YieldTypes)iYield; @@ -2289,10 +2286,10 @@ int BuildRouteCost(const CvAStarNode* /*parent*/, const CvAStarNode* node, const } //too dangerous, might be severed any time - if (pPlot->getOwner() == NO_PLAYER && pPlot->IsAdjacentOwnedByTeamOtherThan(pPlayer->getTeam())) + if (pPlot->getOwner() == NO_PLAYER && pPlot->IsAdjacentOwnedByTeamOtherThan(pPlayer->getTeam(), false, false, true, true)) iCost *= 3; - if (data.bIsForCapital) + if (data.bBenefitsVillages) iCost -= BuildRouteVillageBonus(pPlayer, pPlot, eRouteType, eBuilderTaskingAi); if (iCost < 0) @@ -2301,26 +2298,38 @@ int BuildRouteCost(const CvAStarNode* /*parent*/, const CvAStarNode* node, const return iCost; } -bool IsSafeForRoute(CvPlot* pPlot, CvPlayer* ePlayer) +static bool IsSafeForRoute(CvPlot* pPlot, CvPlayer* pPlayer) { + TeamTypes ePlotTeam = pPlot->getTeam(); + TeamTypes ePlayerTeam = pPlayer->getTeam(); + PlayerTypes ePlotOwner = pPlot->getOwner(); + // Our plots and surrounding plots are safe - if (pPlot->getTeam() == ePlayer->getTeam() || pPlot->isAdjacentTeam(ePlayer->getTeam(), false)) + if (ePlotTeam == ePlayerTeam || pPlot->isAdjacentTeam(pPlayer->getTeam(), false)) + { + return true; + } + + // Our vassal's plots and surrounding plots are safe + if (GET_TEAM(ePlotTeam).IsVassal(ePlayerTeam) || pPlot->isAdjacentOwnedByVassal(ePlayerTeam, false)) { return true; } // City state plots and surrounding plots are safe - if (pPlot->isOwned() && GET_PLAYER(pPlot->getOwner()).isMinorCiv() && !GET_PLAYER(pPlot->getOwner()).GetMinorCivAI()->IsAtWarWithPlayersTeam(ePlayer->GetID())) + if (ePlotOwner != NO_PLAYER && GET_PLAYER(ePlotOwner).isMinorCiv() && !GET_PLAYER(ePlotOwner).GetMinorCivAI()->IsAtWarWithPlayersTeam(pPlayer->GetID())) { return true; } CvPlot** aPlotsToCheck = GC.getMap().getNeighborsUnchecked(pPlot); + PlayerTypes eAdjacentOwner; for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++) { CvPlot* pAdjacentPlot = aPlotsToCheck[iI]; if (pAdjacentPlot != NULL) { - if (pAdjacentPlot->isOwned() && GET_PLAYER(pAdjacentPlot->getOwner()).isMinorCiv() && !GET_PLAYER(pAdjacentPlot->getOwner()).GetMinorCivAI()->IsAtWarWithPlayersTeam(ePlayer->GetID())) + eAdjacentOwner = pAdjacentPlot->getOwner(); + if (eAdjacentOwner != NO_PLAYER && GET_PLAYER(eAdjacentOwner).isMinorCiv() && !GET_PLAYER(eAdjacentOwner).GetMinorCivAI()->IsAtWarWithPlayersTeam(pPlayer->GetID())) { return true; } @@ -2357,7 +2366,7 @@ int BuildRouteValid(const CvAStarNode* parent, const CvAStarNode* node, const SP return FALSE; PlayerTypes ePlotOwnerPlayer = pNewPlot->getOwner(); - if(ePlotOwnerPlayer != NO_PLAYER && pNewPlot->getTeam() != thisPlayer.getTeam()) + if(ePlotOwnerPlayer != NO_PLAYER && pNewPlot->getTeam() != thisPlayer.getTeam() && !GET_TEAM(pNewPlot->getTeam()).IsVassal(thisPlayer.getTeam())) { PlayerTypes eMajorPlayer = NO_PLAYER; PlayerTypes eMinorPlayer = NO_PLAYER; @@ -2376,11 +2385,6 @@ int BuildRouteValid(const CvAStarNode* parent, const CvAStarNode* node, const SP { return FALSE; } - - if(!GET_PLAYER(eMinorPlayer).GetMinorCivAI()->IsActiveQuestForPlayer(eMajorPlayer, MINOR_CIV_QUEST_ROUTE)) - { - return FALSE; - } } //Free routes from traits are always safe @@ -3715,7 +3719,7 @@ SPathFinderUserData::SPathFinderUserData(const CvUnit* pUnit, int _iFlags, int _ iMaxNormalizedDistance = INT_MAX; iMinMovesLeft = 0; iStartMoves = pUnit ? pUnit->getMoves() : 0; - bIsForCapital = false; + bBenefitsVillages = false; } // --------------------------------------------------------------------------- @@ -3733,7 +3737,7 @@ SPathFinderUserData::SPathFinderUserData(PlayerTypes _ePlayer, PathType _ePathTy iMaxNormalizedDistance = INT_MAX; iMinMovesLeft = 0; iStartMoves = 0; - bIsForCapital = false; + bBenefitsVillages = false; } // --------------------------------------------------------------------------- @@ -3751,7 +3755,7 @@ SPathFinderUserData::SPathFinderUserData(PlayerTypes _ePlayer, PathType _ePathTy iMaxNormalizedDistance = INT_MAX; iMinMovesLeft = 0; iStartMoves = 0; - bIsForCapital = false; + bBenefitsVillages = false; } // --------------------------------------------------------------------------- @@ -3769,12 +3773,12 @@ SPathFinderUserData::SPathFinderUserData(PlayerTypes _ePlayer, PathType _ePathTy iMaxNormalizedDistance = INT_MAX; iMinMovesLeft = 0; iStartMoves = 0; - bIsForCapital = false; + bBenefitsVillages = false; } // --------------------------------------------------------------------------- //convenience constructor -SPathFinderUserData::SPathFinderUserData(PlayerTypes _ePlayer, PathType _ePathType, BuildTypes _eBuildType, RouteTypes _eRouteType, bool _bIsForCapital) +SPathFinderUserData::SPathFinderUserData(PlayerTypes _ePlayer, PathType _ePathType, BuildTypes _eBuildType, RouteTypes _eRouteType, bool _bBenefitsVillages) { ePathType = _ePathType; iFlags = 0; @@ -3787,7 +3791,7 @@ SPathFinderUserData::SPathFinderUserData(PlayerTypes _ePlayer, PathType _ePathTy iMaxNormalizedDistance = INT_MAX; iMinMovesLeft = 0; iStartMoves = 0; - bIsForCapital = _bIsForCapital; + bBenefitsVillages = _bBenefitsVillages; } CvPlot * SPath::get(int i) const diff --git a/CvGameCoreDLL_Expansion2/CvAStarNode.h b/CvGameCoreDLL_Expansion2/CvAStarNode.h index 9888ffd674..c13f9c2d40 100644 --- a/CvGameCoreDLL_Expansion2/CvAStarNode.h +++ b/CvGameCoreDLL_Expansion2/CvAStarNode.h @@ -136,12 +136,12 @@ class CvAStarNode //------------------------------------------------------------------------------------------------- struct SPathFinderUserData { - SPathFinderUserData() : ePathType(PT_GENERIC_SAME_AREA), iFlags(0), ePlayer(NO_PLAYER), eEnemy(NO_PLAYER), iUnitID(0), eBuildType(NO_BUILD), eRouteType(NO_ROUTE), bIsForCapital(false), iMaxTurns(INT_MAX), iMaxNormalizedDistance(INT_MAX), iMinMovesLeft(0), iStartMoves(60) {} + SPathFinderUserData() : ePathType(PT_GENERIC_SAME_AREA), iFlags(0), ePlayer(NO_PLAYER), eEnemy(NO_PLAYER), iUnitID(0), eBuildType(NO_BUILD), eRouteType(NO_ROUTE), bBenefitsVillages(false), iMaxTurns(INT_MAX), iMaxNormalizedDistance(INT_MAX), iMinMovesLeft(0), iStartMoves(60) {} SPathFinderUserData(const CvUnit* pUnit, int iFlags=0, int iMaxTurns=INT_MAX); // PT_AIR_REBASE (special case, set after construction) SPathFinderUserData(PlayerTypes ePlayer, PathType ePathType); // PT_TRADE_WATER, PT_TRADE_LAND, PT_LANDMASS_CONNECTION, PT_CITY_CONNECTION_WATER SPathFinderUserData(PlayerTypes ePlayer, PathType ePathType, int iMaxTurns); // PT_AREA_CONNECTION (iMaxTurns is simple vs complex check (0/1)), PT_CITY_INFLUENCE SPathFinderUserData(PlayerTypes ePlayer, PathType ePathType, PlayerTypes eEnemy, int iMaxTurns); // PT_GENERIC_SAME_AREA, PT_GENERIC_SAME_AREA_WIDE, PT_ARMY_LAND, PT_ARMY_WATER, PT_ARMY_MIXED - SPathFinderUserData(PlayerTypes ePlayer, PathType ePathType, BuildTypes eBuildType, RouteTypes eRouteType, bool bIsForCapital); // PT_BUILD_ROUTE, PT_BUILD_ROUTE_MIXED, PT_CITY_CONNECTION_LAND, PT_CITY_CONNECTION_MIXED + SPathFinderUserData(PlayerTypes ePlayer, PathType ePathType, BuildTypes eBuildType, RouteTypes eRouteType, bool bBenefitsVillages); // PT_BUILD_ROUTE, PT_BUILD_ROUTE_MIXED, PT_CITY_CONNECTION_LAND, PT_CITY_CONNECTION_MIXED //do not compare max turns and max cost ... bool operator==(const SPathFinderUserData& rhs) const @@ -151,7 +151,7 @@ struct SPathFinderUserData PathType ePathType; RouteTypes eRouteType; BuildTypes eBuildType; //BuildType related to the RouteType - bool bIsForCapital; + bool bBenefitsVillages; int iFlags; //see CvUnit::MOVEFLAG* PlayerTypes ePlayer; //optional PlayerTypes eEnemy; diff --git a/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.cpp b/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.cpp index 6dce2368d9..681b3129f1 100644 --- a/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.cpp +++ b/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.cpp @@ -41,7 +41,6 @@ void CvBuilderTaskingAI::Init(CvPlayer* pPlayer) m_eFalloutFeature = GetFalloutFeature(); m_eFalloutRemove = GetFalloutRemove(); m_eRemoveRouteBuild = GetRemoveRoute(); - m_eRouteBuild = NO_BUILD; //will be updated each turn! m_bLogging = GC.getLogging() && GC.getAILogging() && GC.GetBuilderAILogging(); @@ -200,8 +199,6 @@ void CvBuilderTaskingAI::Update(void) UpdateRoutePlots(); UpdateCanalPlots(); - m_eRouteBuild = GetBuildRoute(); - if(m_bLogging) { bool bShowOutput = m_pPlayer->isHuman(); @@ -260,7 +257,7 @@ int GetPlotYield(CvPlot* pPlot, YieldTypes eYield) return pPlot->calculateNatureYield(eYield, NO_PLAYER, NULL); } -void CvBuilderTaskingAI::ConnectCitiesToCapital(CvCity* pPlayerCapital, CvCity* pTargetCity, BuildTypes eBuild, RouteTypes eRoute, int iNetGoldTimes100) +void CvBuilderTaskingAI::ConnectCitiesToCapital(CvCity* pPlayerCapital, CvCity* pTargetCity, BuildTypes eBuild, RouteTypes eRoute) { if(pTargetCity->IsRazing()) { @@ -289,12 +286,16 @@ void CvBuilderTaskingAI::ConnectCitiesToCapital(CvCity* pPlayerCapital, CvCity* } // for quests we might be targeting a city state ... - bool bSamePlayer = pTargetCity->getOwner() == pPlayerCapital->getOwner(); + bool bTargetingMinor = GET_TEAM(pTargetCity->getTeam()).isMinorCiv(); + + // There's no reason for building railroads for city state quests + if (bTargetingMinor && eRoute == ROUTE_RAILROAD) + return; bool bHuman = m_pPlayer->isHuman(); // go through the route to see how long it is and how many plots already have roads int iRoadLength = 0; - int iPlotsNeeded = 0; + int iRoadMaintenanceLength = 0; for (size_t i=0; igetOwner() != pCity2->getOwner()) return; - // don't build shortcut routes when we are losing money - if (iNetGoldTimes100 < 0) - return; - // don't connect razing cities if (pCity1->IsRazing() || pCity2->IsRazing()) return; @@ -413,7 +410,7 @@ void CvBuilderTaskingAI::ConnectCitiesForShortcuts(CvCity* pCity1, CvCity* pCity } // build a path between the two cities - this will tend to re-use existing routes, unless the new path is much shorter - SPathFinderUserData data(m_pPlayer->GetID(),PT_BUILD_ROUTE,eBuild,eRoute,false); + SPathFinderUserData data(m_pPlayer->GetID(),PT_BUILD_ROUTE,eBuild,eRoute,true); SPath newPath = GC.GetStepFinder().GetPath(pCity1->getX(), pCity1->getY(), pCity2->getX(), pCity2->getY(), data); // cities are not on the same landmass? then give up @@ -422,7 +419,7 @@ void CvBuilderTaskingAI::ConnectCitiesForShortcuts(CvCity* pCity1, CvCity* pCity // go through the route to see how long it is and how many plots already have roads int iRoadLength = 0; - int iPlotsNeeded = 0; + int iRoadMaintenanceLength = 0; for (size_t i = 0; i < newPath.vPlots.size(); i++) { @@ -442,8 +439,8 @@ void CvBuilderTaskingAI::ConnectCitiesForShortcuts(CvCity* pCity1, CvCity* pCity iRoadLength++; - if ((pPlot->getRouteType() < eRoute || pPlot->IsRoutePillaged()) && !GetSameRouteBenefitFromTrait(pPlot, eRoute)) - iPlotsNeeded++; + if (!GetSameRouteBenefitFromTrait(pPlot, eRoute)) + iRoadMaintenanceLength++; } int iMaintenancePerTile = pRouteInfo->GetGoldMaintenance() * (100 + m_pPlayer->GetImprovementGoldMaintenanceMod()); @@ -457,9 +454,9 @@ void CvBuilderTaskingAI::ConnectCitiesForShortcuts(CvCity* pCity1, CvCity* pCity iSideBenefits += iRoadLength * 150; } - int iProfit = iSideBenefits - (iRoadLength * iMaintenancePerTile); + int iProfit = iSideBenefits - (iRoadMaintenanceLength * iMaintenancePerTile); - if (iProfit < 0 && iProfit + iNetGoldTimes100 < 0) + if (iProfit < 0) return; for (size_t i=0; iGetCityConnections(); + m_routeNeededPlots.clear(); + m_routeWantedPlots.clear(); + m_mainRoutePlots.clear(); + m_shortcutRoutePlots.clear(); + m_strategicRoutePlots.clear(); + // if there are fewer than 2 cities, we don't need to run this function std::vector plotsToConnect = pCityConnections->GetPlotsToConnect(); if(plotsToConnect.size() < 2) @@ -779,12 +782,6 @@ void CvBuilderTaskingAI::UpdateRoutePlots(void) return; } - m_routeNeededPlots.clear(); - m_routeWantedPlots.clear(); - m_mainRoutePlots.clear(); - m_shortcutRoutePlots.clear(); - m_strategicRoutePlots.clear(); - for(int i = GC.getNumBuildInfos() - 1; i >= 0; i--) { BuildTypes eBuild = (BuildTypes)i; @@ -859,7 +856,7 @@ void CvBuilderTaskingAI::UpdateRoutePlots(void) { //if we already have a connection to the capital, it may be possible to have a much shorter route for a direct connection //thus improving unit movement and gold bonus from villages - ConnectCitiesForShortcuts(pFirstCity, pSecondCity, eBuild, eRoute, iNetGoldTimes100); + ConnectCitiesForShortcuts(pFirstCity, pSecondCity, eBuild, eRoute); } else { @@ -874,7 +871,7 @@ void CvBuilderTaskingAI::UpdateRoutePlots(void) pTargetCity = pFirstCity; } - ConnectCitiesToCapital(pPlayerCapitalCity, pTargetCity, eBuild, eRoute, iNetGoldTimes100); + ConnectCitiesToCapital(pPlayerCapitalCity, pTargetCity, eBuild, eRoute); } } else @@ -1522,22 +1519,25 @@ void CvBuilderTaskingAI::AddRemoveRouteDirectives(vectorgetBestRoute(); - if (eBestRouteType == NO_ROUTE) + if (m_pPlayer->getBestRoute() == NO_ROUTE) return; - if (pPlot->getRouteType() == NO_ROUTE) + RouteTypes eRoute = pPlot->getRouteType(); + + if (eRoute == NO_ROUTE) return; if (pPlot->isCity()) return; + RouteTypes eWantedRoute = GetRouteTypeWantedAtPlot(pPlot); + // the plot was flagged recently, so ignore - if (WantRouteAtPlot(pPlot)) + if (eWantedRoute >= eRoute) return; // keep routes which are needed - if (NeedRouteAtPlot(pPlot) && !GetSameRouteBenefitFromTrait(pPlot, pPlot->getRouteType())) + if (GetRouteTypeNeededAtPlot(pPlot) >= eRoute && !GetSameRouteBenefitFromTrait(pPlot, eWantedRoute)) { return; } @@ -1546,22 +1546,22 @@ void CvBuilderTaskingAI::AddRemoveRouteDirectives(vectorIsRoutePillaged()) return; - // don't remove routes which we did not create - if (pPlot->GetPlayerResponsibleForRoute() != NO_PLAYER && pPlot->GetPlayerResponsibleForRoute() != m_pPlayer->GetID()) + // don't remove routes which we do not pay maintenance for + PlayerTypes eRouteResponsible = pPlot->GetPlayerResponsibleForRoute(); + if (eRouteResponsible != m_pPlayer->GetID()) return; - //don't touch master's roads! - if (pPlot->GetPlayerResponsibleForRoute() != NO_PLAYER && pPlot->GetPlayerResponsibleForRoute() != m_pPlayer->GetID()) - if (GET_TEAM(m_pPlayer->getTeam()).IsVassal(GET_PLAYER(pPlot->GetPlayerResponsibleForRoute()).getTeam())) - return; + // don't remove routes that we pay maintenance for if our master built them + if (eRouteResponsible == m_pPlayer->GetID() && GET_TEAM(m_pPlayer->getTeam()).IsVassal(GET_PLAYER(pPlot->GetPlayerThatBuiltRoute()).getTeam())) + return; //we want to be aggressive with this because of the cost. - int iWeight = /*500*/ GD_INT_GET(BUILDER_TASKING_BASELINE_BUILD_ROUTES) * 2 / 3; + int iWeight = /*500*/ GD_INT_GET(BUILDER_TASKING_BASELINE_BUILD_ROUTES) * 2; //if we are in debt, be more aggressive EconomicAIStrategyTypes eStrategyLosingMoney = (EconomicAIStrategyTypes)GC.getInfoTypeForString("ECONOMICAISTRATEGY_LOSING_MONEY"); - if (!m_pPlayer->GetEconomicAI()->IsUsingStrategy(eStrategyLosingMoney)) - iWeight *= 2; + if (m_pPlayer->GetEconomicAI()->IsUsingStrategy(eStrategyLosingMoney)) + iWeight *= 3; iWeight = GetBuildCostWeight(iWeight, pPlot, m_eRemoveRouteBuild); iWeight += GetBuildTimeWeight(pUnit, pPlot, m_eRemoveRouteBuild, false); @@ -1597,22 +1597,23 @@ void CvBuilderTaskingAI::AddRemoveRouteDirectives(vector>& directives, CvUnit* pUnit, CvPlot* pPlot, CvCity* /*pCity*/, int iMoveTurnsAway) { - // if the player can't build a route, bail out! - RouteTypes eBestRouteType = m_pPlayer->getBestRoute(); - if(eBestRouteType == NO_ROUTE) + // the plot was not flagged recently, so ignore + if (!WantRouteAtPlot(pPlot)) return; + RouteTypes eRoute = GetRouteTypeWantedAtPlot(pPlot); + if(eRoute == NO_ROUTE) + return; + + BuildTypes eRouteBuild = GetBuildRoute(eRoute); + // can we even build the desired route CvUnitEntry& kUnitInfo = pUnit->getUnitInfo(); - if(m_eRouteBuild == NO_BUILD || !kUnitInfo.GetBuilds(m_eRouteBuild)) + if(eRouteBuild == NO_BUILD || !kUnitInfo.GetBuilds(eRouteBuild)) return; // no matter if pillaged or not - if(pPlot->getRouteType() == eBestRouteType) - return; - - // the plot was not flagged recently, so ignore - if (!WantRouteAtPlot(pPlot)) + if(pPlot->getRouteType() >= eRoute) return; if(GET_PLAYER(pUnit->getOwner()).isOption(PLAYEROPTION_LEAVE_FORESTS)) @@ -1620,7 +1621,7 @@ void CvBuilderTaskingAI::AddRouteDirectives(vectorgetFeatureType(); if(eFeature != NO_FEATURE) { - CvBuildInfo* pkBuild = GC.getBuildInfo(m_eRouteBuild); + CvBuildInfo* pkBuild = GC.getBuildInfo(eRouteBuild); if(pkBuild && pkBuild->isFeatureRemove(eFeature)) { return; @@ -1631,15 +1632,15 @@ void CvBuilderTaskingAI::AddRouteDirectives(vectorplot(), *pPlot); //tiebreaker in case of multiple equal options BuilderDirective directive; directive.m_eDirective = eDirectiveType; - directive.m_eBuild = m_eRouteBuild; + directive.m_eBuild = eRouteBuild; directive.m_eResource = NO_RESOURCE; directive.m_sX = pPlot->getX(); directive.m_sY = pPlot->getY(); @@ -1985,8 +1986,8 @@ bool CvBuilderTaskingAI::ShouldBuilderConsiderPlot(CvUnit* pUnit, CvPlot* pPlot) return false; } - //can't build in other major player's territory - if (pPlot->isOwned() && pPlot->getOwner()!=pUnit->getOwner() && GET_PLAYER(pPlot->getOwner()).isMajorCiv()) + //can't build in other major player's territory (unless they are our vassal) + if (pPlot->isOwned() && pPlot->getOwner()!=pUnit->getOwner() && GET_PLAYER(pPlot->getOwner()).isMajorCiv() && !GET_TEAM(pPlot->getTeam()).IsVassal(m_pPlayer->getTeam())) return false; // workers should not be able to work in plots that do not match their default domain @@ -2048,7 +2049,7 @@ bool CvBuilderTaskingAI::ShouldBuilderConsiderPlot(CvUnit* pUnit, CvPlot* pPlot) } //danger check is not enough - we don't want to be adjacent to enemy territory for example - if (pPlot->isVisibleToEnemy(pUnit->getOwner())) + if (m_pPlayer->GetTacticalAI()->IsVisibleToEnemy(pPlot)) return false; if (!pUnit->canEndTurnAtPlot(pPlot)) @@ -2748,7 +2749,8 @@ int CvBuilderTaskingAI::ScorePlotBuild(CvUnit* pUnit, CvPlot* pPlot, Improvement } if (iRouteScore > 0) { - if (pPlot->IsCityConnection(m_pPlayer->GetID()) && (pPlot->IsRouteRailroad() || pPlot->IsRouteRoad())) + // If we don't need a route at this plot, it is about to be removed + if (pPlot->IsCityConnection(m_pPlayer->GetID()) && (pPlot->IsRouteRailroad() || pPlot->IsRouteRoad()) && NeedRouteAtPlot(pPlot)) iSecondaryScore += iRouteScore; else iSecondaryScore -= iRouteScore; @@ -2948,17 +2950,16 @@ BuildTypes CvBuilderTaskingAI::GetRemoveRoute(void) return eRemoveRouteBuild; } -BuildTypes CvBuilderTaskingAI::GetBuildRoute(void) +BuildTypes CvBuilderTaskingAI::GetBuildRoute(RouteTypes eRoute) { // find the route build BuildTypes eRouteBuild = NO_BUILD; - RouteTypes eBestRoute = m_pPlayer->getBestRoute(); for(int i = 0; i < GC.getNumBuildInfos(); i++) { BuildTypes eBuild = (BuildTypes)i; CvBuildInfo* pkBuild = GC.getBuildInfo(eBuild); - if(pkBuild && pkBuild->getRoute() == eBestRoute) + if(pkBuild && pkBuild->getRoute() == eRoute) { eRouteBuild = eBuild; break; diff --git a/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.h b/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.h index 68aec96f4d..091190b838 100644 --- a/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.h +++ b/CvGameCoreDLL_Expansion2/CvBuilderTaskingAI.h @@ -106,7 +106,7 @@ class CvBuilderTaskingAI FeatureTypes GetFalloutFeature(void); BuildTypes GetFalloutRemove(void); BuildTypes GetRemoveRoute(void); - BuildTypes GetBuildRoute(void); + BuildTypes GetBuildRoute(RouteTypes eRoute); static void LogInfo(const CvString& str, CvPlayer* pPlayer, bool bWriteToOutput = false); static void LogYieldInfo(const CvString& strNewLogStr, CvPlayer* pPlayer); //Log yield related info to BuilderTaskingYieldLog.csv. @@ -121,8 +121,8 @@ class CvBuilderTaskingAI void LogDirectives(vector> directives, CvUnit* pUnit); void LogDirective(BuilderDirective directive, CvUnit* pUnit, int iWeight, bool bChosen = false); - void ConnectCitiesToCapital(CvCity* pPlayerCapital, CvCity* pTargetCity, BuildTypes eBuild, RouteTypes eRoute, int iNetGoldTimes100); - void ConnectCitiesForShortcuts(CvCity* pFirstCity, CvCity* pSecondCity, BuildTypes eBuild, RouteTypes eRoute, int iNetGoldTimes100); + void ConnectCitiesToCapital(CvCity* pPlayerCapital, CvCity* pTargetCity, BuildTypes eBuild, RouteTypes eRoute); + void ConnectCitiesForShortcuts(CvCity* pFirstCity, CvCity* pSecondCity, BuildTypes eBuild, RouteTypes eRoute); void ConnectCitiesForScenario(CvCity* pFirstCity, CvCity* pSecondCity, BuildTypes eBuild, RouteTypes eRoute); void ConnectPointsForStrategy(CvCity* pOriginCity, CvPlot* pTargetPlot, BuildTypes eBuild, RouteTypes eRoute, int iNetGoldTimes100); @@ -154,7 +154,6 @@ class CvBuilderTaskingAI FeatureTypes m_eFalloutFeature; BuildTypes m_eFalloutRemove; BuildTypes m_eRemoveRouteBuild; - BuildTypes m_eRouteBuild; //some player dependent flags for unique improvements bool m_bKeepMarshes; diff --git a/CvGameCoreDLL_Expansion2/CvDangerPlots.cpp b/CvGameCoreDLL_Expansion2/CvDangerPlots.cpp index c1481f579c..cb70357831 100644 --- a/CvGameCoreDLL_Expansion2/CvDangerPlots.cpp +++ b/CvGameCoreDLL_Expansion2/CvDangerPlots.cpp @@ -812,7 +812,7 @@ int CvDangerPlotContents::GetDanger(const CvUnit* pUnit, const UnitIdContainer& { int iDummy = 0; int iDamage = TacticalAIHelpers::GetSimulatedDamageFromAttackOnUnit(pUnit, pAttacker, m_pPlot, pAttacker->plot(), iDummy, false, 0, true); - if (!m_pPlot->isVisible(pAttacker->getTeam())) + if (!GET_PLAYER(pUnit->getOwner()).GetTacticalAI()->IsVisibleToPlayer(m_pPlot, pAttacker->getTeam())) iDamage = (iDamage * 80) / 100; //there's a chance they won't spot us iPlotDamage += iDamage; } @@ -895,7 +895,7 @@ int CvDangerPlotContents::GetDanger(const CvUnit* pUnit, const UnitIdContainer& //if the attacker is not out of range, assume they need to move for the attack, so we don't know their plot int iDamage = TacticalAIHelpers::GetSimulatedDamageFromAttackOnUnit(pUnit, pAttacker, m_pPlot, bOutOfRange ? pAttacker->plot() : NULL, iAttackerDamage, false, iExtraDamage, true); - if (!m_pPlot->isVisible(pAttacker->getTeam())) + if (!GET_PLAYER(pUnit->getOwner()).GetTacticalAI()->IsVisibleToPlayer(m_pPlot, pAttacker->getTeam())) iDamage = (iDamage * 80) / 100; //there's a chance they won't spot us if (iAttackerDamage >= pAttacker->GetCurrHitPoints() && !pAttacker->isSuicide()) diff --git a/CvGameCoreDLL_Expansion2/CvPlayerAI.cpp b/CvGameCoreDLL_Expansion2/CvPlayerAI.cpp index 0892edf4fb..8d080cd491 100644 --- a/CvGameCoreDLL_Expansion2/CvPlayerAI.cpp +++ b/CvGameCoreDLL_Expansion2/CvPlayerAI.cpp @@ -287,6 +287,7 @@ void CvPlayerAI::AI_unitUpdate() // just been handed off to the tactical AI to get a move in the same turn they switch between GetTacticalAI()->Update(); GetHomelandAI()->Update(); + GetTacticalAI()->CleanUp(); } } diff --git a/CvGameCoreDLL_Expansion2/CvPlot.cpp b/CvGameCoreDLL_Expansion2/CvPlot.cpp index 25b06433b0..d0e9daf708 100644 --- a/CvGameCoreDLL_Expansion2/CvPlot.cpp +++ b/CvGameCoreDLL_Expansion2/CvPlot.cpp @@ -204,6 +204,7 @@ void CvPlot::reset() m_eResourceType = NO_RESOURCE; m_eImprovementType = NO_IMPROVEMENT; m_ePlayerBuiltImprovement = NO_PLAYER; + m_ePlayerBuiltRoute = NO_PLAYER; m_ePlayerResponsibleForImprovement = NO_PLAYER; m_ePlayerResponsibleForRoute = NO_PLAYER; m_ePlayerThatClearedBarbCampHere = NO_PLAYER; @@ -3433,19 +3434,27 @@ bool CvPlot::isAdjacentPlayer(PlayerTypes ePlayer, bool bLandOnly) const } // -------------------------------------------------------------------------------- -bool CvPlot::IsAdjacentOwnedByTeamOtherThan(TeamTypes eTeam, bool bAllowNoTeam, bool bIgnoreImpassable) const +bool CvPlot::IsAdjacentOwnedByTeamOtherThan(TeamTypes eTeam, bool bAllowNoTeam, bool bIgnoreImpassable, bool bIgnoreMinor, bool bIgnoreVassal) const { CvPlot** aPlotsToCheck = GC.getMap().getNeighborsUnchecked(this); for(int iI=0; iIgetTeam() != eTeam) - { - if (bIgnoreImpassable && pAdjacentPlot->isImpassable(pAdjacentPlot->getTeam())) - continue; + if(pAdjacentPlot == NULL) + continue; + + if (bIgnoreImpassable && pAdjacentPlot->isImpassable(pAdjacentPlot->getTeam())) + continue; + + if (bIgnoreMinor && GET_PLAYER(pAdjacentPlot->getOwner()).isMinorCiv()) + continue; - if (bAllowNoTeam || pAdjacentPlot->getTeam() != NO_TEAM) - return true; + if (bIgnoreVassal && GET_TEAM(pAdjacentPlot->getTeam()).IsVassal(eTeam)) + continue; + + if (pAdjacentPlot->getTeam() != eTeam && (pAdjacentPlot->getTeam() != NO_TEAM || bAllowNoTeam)) + { + return true; } } @@ -3535,6 +3544,28 @@ bool CvPlot::IsAdjacentOwnedByEnemy(TeamTypes eTeam) const return false; } +// -------------------------------------------------------------------------------- +bool CvPlot::isAdjacentOwnedByVassal(TeamTypes eTeam, bool bLandOnly) const +{ + CvPlot** aPlotsToCheck = GC.getMap().getNeighborsUnchecked(this); + for (int iI=0; iIgetTeam() != NO_TEAM && GET_TEAM(pAdjacentPlot->getTeam()).IsVassal(eTeam)) + { + if(!bLandOnly || !(pAdjacentPlot->isWater())) + { + return true; + } + } + } + } + + return false; +} + // -------------------------------------------------------------------------------- bool CvPlot::isAdjacentTeam(TeamTypes eTeam, bool bLandOnly) const { @@ -4085,26 +4116,6 @@ bool CvPlot::isVisibleToAnyTeam(bool bNoMinor) const return false; } -// -------------------------------------------------------------------------------- -bool CvPlot::isVisibleToEnemy(PlayerTypes eFriendlyPlayer) const -{ - const std::vector& vEnemies = GET_PLAYER(eFriendlyPlayer).GetPlayersAtWarWith(); - - for (std::vector::const_iterator it = vEnemies.begin(); it != vEnemies.end(); ++it) - { - CvPlayer& kEnemy = GET_PLAYER(*it); - if (kEnemy.isAlive() && kEnemy.IsAtWarWith(eFriendlyPlayer)) - { - if (isVisible(kEnemy.getTeam())) - { - return true; - } - } - } - - return false; -} - // -------------------------------------------------------------------------------- bool CvPlot::isVisibleToWatchingHuman() const @@ -7352,6 +7363,7 @@ void CvPlot::setIsCity(bool bValue, int iCityID, int iWorkRange) { // Maintenance change! SetPlayerResponsibleForRoute(getOwner()); + SetPlayerThatBuiltRoute(getOwner()); } // plot ownership will be changed in CvCity::preKill @@ -8701,6 +8713,9 @@ void CvPlot::setRouteType(RouteTypes eNewValue, PlayerTypes eBuilder) SetPlayerResponsibleForRoute(NO_PLAYER); } + if(eOldRoute != eNewValue) + SetPlayerThatBuiltRoute(eBuilder); + // Route switch here! m_eRouteType = eNewValue; @@ -8825,6 +8840,20 @@ void CvPlot::SetPlayerThatBuiltImprovement(PlayerTypes eBuilder) m_ePlayerBuiltImprovement = eBuilder; } +// -------------------------------------------------------------------------------- +/// Who built this Road? Could be NO_PLAYER +PlayerTypes CvPlot::GetPlayerThatBuiltRoute() const +{ + return (PlayerTypes)m_ePlayerBuiltRoute; +} + +// -------------------------------------------------------------------------------- +/// Who built this Improvement? Could be NO_PLAYER +void CvPlot::SetPlayerThatBuiltRoute(PlayerTypes eBuilder) +{ + m_ePlayerBuiltRoute= eBuilder; +} + // -------------------------------------------------------------------------------- /// Who pays maintenance for this Improvement? PlayerTypes CvPlot::GetPlayerResponsibleForImprovement() const @@ -11293,6 +11322,15 @@ bool CvPlot::setRevealed(TeamTypes eTeam, bool bNewValue, CvUnit* pUnit, bool bT bool bVisbilityUpdated = false; bool bRevealed = isRevealed(eTeam) != bNewValue; + + // Update tactical AI, let it know that the tile was made visible + const CivsList pPlayers = GET_TEAM(eTeam).getPlayers(); + for (size_t iJ = 0; iJ < pPlayers.size(); iJ++) + { + if (pPlayers[iJ] == GC.getGame().getActivePlayer()) + GET_PLAYER(pPlayers[iJ]).GetTacticalAI()->NewVisiblePlot(this, bRevealed); + } + if(bRevealed) { bVisbilityUpdated = true; @@ -12852,6 +12890,7 @@ void CvPlot::Serialize(Plot& plot, Visitor& visitor) visitor(plot.m_ePlayerBuiltImprovement); visitor(plot.m_ePlayerResponsibleForImprovement); + visitor(plot.m_ePlayerBuiltRoute); visitor(plot.m_ePlayerResponsibleForRoute); visitor(plot.m_ePlayerThatClearedBarbCampHere); visitor(plot.m_ePlayerThatClearedDigHere); diff --git a/CvGameCoreDLL_Expansion2/CvPlot.h b/CvGameCoreDLL_Expansion2/CvPlot.h index 23d58cb6b6..f4ddb86930 100644 --- a/CvGameCoreDLL_Expansion2/CvPlot.h +++ b/CvGameCoreDLL_Expansion2/CvPlot.h @@ -208,9 +208,10 @@ class CvPlot bool isAdjacentOwned() const; bool isAdjacentPlayer(PlayerTypes ePlayer, bool bLandOnly = false) const; - bool IsAdjacentOwnedByTeamOtherThan(TeamTypes eTeam, bool bAllowNoTeam=false, bool bIgnoreImpassable=false) const; + bool IsAdjacentOwnedByTeamOtherThan(TeamTypes eTeam, bool bAllowNoTeam=false, bool bIgnoreImpassable=false, bool bIgnoreMinor=false, bool bIgnoreVassal=false) const; bool IsAdjacentOwnedByUnfriendly(PlayerTypes ePlayer, vector& vUnfriendlyMajors) const; bool IsAdjacentOwnedByEnemy(TeamTypes eTeam) const; + bool isAdjacentOwnedByVassal(TeamTypes eTeam, bool bLandOnly = false) const; bool isAdjacentTeam(TeamTypes eTeam, bool bLandOnly = false) const; bool IsAdjacentCity(TeamTypes eTeam = NO_TEAM) const; CvCity* GetAdjacentFriendlyCity(TeamTypes eTeam, bool bLandOnly = false) const; @@ -243,7 +244,6 @@ class CvPlot bool isActiveVisible() const; bool isVisibleToAnyTeam(bool bNoMinor = false) const; - bool isVisibleToEnemy(PlayerTypes eFriendlyPlayer) const; bool isVisibleToWatchingHuman() const; bool isAdjacentVisible(TeamTypes eTeam, bool bDebug=false) const; bool isAdjacentNonvisible(TeamTypes eTeam) const; @@ -502,6 +502,9 @@ class CvPlot // Who built the improvement in this plot? PlayerTypes GetPlayerThatBuiltImprovement() const; void SetPlayerThatBuiltImprovement(PlayerTypes eBuilder); + + PlayerTypes GetPlayerThatBuiltRoute() const; + void SetPlayerThatBuiltRoute(PlayerTypes eBuilder); // Someone footing the bill for an improvement/route in an unowned plot? PlayerTypes GetPlayerResponsibleForImprovement() const; @@ -970,6 +973,7 @@ class CvPlot char /*ResourceTypes*/ m_eResourceType; char /*ImprovementTypes*/ m_eImprovementType; char /*PlayerTypes*/ m_ePlayerBuiltImprovement; + char /*PlayerTypes*/ m_ePlayerBuiltRoute; char /*PlayerTypes*/ m_ePlayerResponsibleForImprovement; char /*PlayerTypes*/ m_ePlayerResponsibleForRoute; char /*PlayerTypes*/ m_ePlayerThatClearedBarbCampHere; diff --git a/CvGameCoreDLL_Expansion2/CvTacticalAI.cpp b/CvGameCoreDLL_Expansion2/CvTacticalAI.cpp index e166db4a45..849fdb8d26 100644 --- a/CvGameCoreDLL_Expansion2/CvTacticalAI.cpp +++ b/CvGameCoreDLL_Expansion2/CvTacticalAI.cpp @@ -314,6 +314,7 @@ void CvTacticalAI::RecruitUnits() /// Update the AI for units void CvTacticalAI::Update() { + UpdateVisibility(); DropOldFocusAreas(); FindTacticalTargets(); @@ -324,6 +325,14 @@ void CvTacticalAI::Update() ProcessDominanceZones(); } +/// Clear up memory usage +void CvTacticalAI::CleanUp() +{ + m_plotsVisibleToOtherPlayer.clear(); + m_AllTargets.clear(); + m_ZoneTargets.clear(); +} + /// Add a temporary focus of attention around a short-term target void CvTacticalAI::AddFocusArea(CvPlot* pPlot, int iRadius, int iDuration) { @@ -373,17 +382,143 @@ bool CvTacticalAI::IsInFocusArea(const CvPlot* pPlot) const return false; } +/// Setup knowledge of other players' seen plots +void CvTacticalAI::UpdateVisibility() +{ + TeamTypes eTeam = m_pPlayer->getTeam(); + + CvPlot* pLoopPlot; + + for (int iI = 0; iI < GC.getMap().numPlots(); iI++) + { + pLoopPlot = GC.getMap().plotByIndexUnchecked(iI); + + if (!pLoopPlot->isRevealed(eTeam)) + continue; + + UpdateVisibilityFromBorders(pLoopPlot, false); + + if (!pLoopPlot->isVisible(eTeam)) + continue; + + NewVisiblePlot(pLoopPlot, false); + } +} + +/// Check if there are any units owned by other players in this tile and what they can see +void CvTacticalAI::NewVisiblePlot(CvPlot* pPlot, bool bRevealed=false) +{ + if (!pPlot) + return; + + TeamTypes eTeam = m_pPlayer->getTeam(); + + // If we just revealed the tile, check if it or a nearby plot is owned by another player + if (bRevealed) + { + UpdateVisibilityFromBorders(pPlot, bRevealed); + } + + if (pPlot->getNumUnits() > 0) + { + TeamTypes eOtherTeam; + CvUnit* pLoopUnit; + + for (int iI = 0; iI < pPlot->getNumUnits(); iI++) + { + pLoopUnit = pPlot->getUnitByIndex(iI); + eOtherTeam = pLoopUnit->getTeam(); + if (eOtherTeam != eTeam && !pLoopUnit->isInvisible(eTeam, false)) + { + for (int iRange = 2; iRange <= pLoopUnit->visibilityRange(); iRange++) + { + const vector& vPlots = GC.getMap().GetPlotsAtRangeX(pPlot, iRange, true, true); + + for (size_t iJ = 0; iJ < vPlots.size(); iJ++) + { + if ((vPlots[iJ]) == NULL) + continue; + + m_plotsVisibleToOtherPlayer[eOtherTeam].insert(vPlots[iJ]->GetPlotIndex()); + } + } + } + } + } +} + +/// Do we know that the other civ can see this tile? +bool CvTacticalAI::IsVisibleToPlayer(const CvPlot* pPlot, TeamTypes eOther) +{ + return m_plotsVisibleToOtherPlayer.count(eOther) > 0 && m_plotsVisibleToOtherPlayer[eOther].count(pPlot->GetPlotIndex()) > 0; +} + +/// Do we know that an enemy civ can see this tile? +bool CvTacticalAI::IsVisibleToEnemy(const CvPlot* pPlot) +{ + const std::vector& vEnemies = m_pPlayer->GetPlayersAtWarWith(); + + for (std::vector::const_iterator it = vEnemies.begin(); it != vEnemies.end(); ++it) + { + CvPlayer& kEnemy = GET_PLAYER(*it); + if (kEnemy.isAlive() && kEnemy.IsAtWarWith(m_pPlayer->GetID())) + { + if (IsVisibleToPlayer(pPlot, kEnemy.getTeam())) + { + return true; + } + } + } + + return false; +} + // PRIVATE METHODS +void CvTacticalAI::UpdateVisibilityFromBorders(CvPlot* pPlot, bool bRevealed) +{ + TeamTypes eTeam = m_pPlayer->getTeam(); + TeamTypes eOtherTeam = pPlot->getTeam(); + CvPlot** aPlotsToCheck = GC.getMap().getNeighborsUnchecked(pPlot); + CvPlot* pAdjacentPlot; + + if (eOtherTeam != NO_TEAM && eOtherTeam != eTeam) + { + // Plot is owned by another player + m_plotsVisibleToOtherPlayer[eOtherTeam].insert(pPlot->GetPlotIndex()); + + if (bRevealed) + { + for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++) + { + pAdjacentPlot = aPlotsToCheck[iI]; + + if (pAdjacentPlot != NULL && pAdjacentPlot->isVisible(eTeam)) + // We knew about this plot before but we may not previously have known that this player was neighboring it + m_plotsVisibleToOtherPlayer[eOtherTeam].insert(pAdjacentPlot->GetPlotIndex()); + } + } + } + + for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++) + { + pAdjacentPlot = aPlotsToCheck[iI]; + + if (pAdjacentPlot != NULL && pAdjacentPlot->isVisible(eTeam)) { + eOtherTeam = pAdjacentPlot->getTeam(); + + if (eOtherTeam != eTeam && pAdjacentPlot->getTeam() != NO_TEAM) + // Neighbors a plot owned by another player + m_plotsVisibleToOtherPlayer[eOtherTeam].insert(pAdjacentPlot->GetPlotIndex()); + } + } +} + /// Make lists of everything we might want to target with the tactical AI this turn void CvTacticalAI::FindTacticalTargets() { CvPlayerTrade* pPlayerTrade = m_pPlayer->GetTrade(); - // Clear out target list since we rebuild it each turn - m_AllTargets.clear(); - m_ZoneTargets.clear(); - bool bNoBarbsAllowedYet = GC.getGame().getGameTurn() < GC.getGame().GetBarbarianReleaseTurn(); vector vUnfriendlyMajors = m_pPlayer->GetUnfriendlyMajors(); @@ -1205,7 +1340,7 @@ void CvTacticalAI::PlotMovesToSafety(bool bCombatUnits) { // try to flee or hide int iDangerLevel = pUnit->GetDanger(); - if(iDangerLevel > 0 || pUnit->plot()->isVisibleToEnemy(pUnit->getOwner())) + if(iDangerLevel > 0 || IsVisibleToEnemy(pUnit->plot())) { bool bAddUnit = false; if(bCombatUnits) @@ -6396,8 +6531,7 @@ CvPlot* TacticalAIHelpers::FindSafestPlotInReach(const CvUnit* pUnit, bool bAllo iScore += 12; //try to hide - if there are few enemy units, this might be a tiebreaker - //this is cheating a bit, really we need to check if the plot is visible for the enemy units visible to us - if (!pPlot->isVisibleToEnemy(pUnit->getOwner())) + if (!GET_PLAYER(pUnit->getOwner()).GetTacticalAI()->IsVisibleToEnemy(pPlot)) iScore -= iScore / 4; //try to go avoid borders @@ -7668,7 +7802,7 @@ int ScoreTurnEnd(const CvUnit* pUnit, eUnitAssignmentType eLastAssignment, const if (testPlot.hasAirCover()) iResult+=3; //when in doubt, hide from the enemy - if (!testPlot.isVisibleToEnemy()) + if (!GET_PLAYER(pUnit->getOwner()).GetTacticalAI()->IsVisibleToEnemy(testPlot.getPlot())) iResult++; //try to occupy enemy citadels! @@ -8194,7 +8328,7 @@ CvTacticalPlot::CvTacticalPlot(const CvPlot* plot, PlayerTypes ePlayer, const ve //constant bfBlockedByNonSimCombatUnit = 0; bHasAirCover = pPlot->HasAirCover(ePlayer); - bIsVisibleToEnemy = pPlot->isVisibleToEnemy(ePlayer); + bIsVisibleToEnemy = kPlayer.GetTacticalAI()->IsVisibleToEnemy(pPlot); //updated if necessary bEdgeOfTheKnownWorldUnknown = true; diff --git a/CvGameCoreDLL_Expansion2/CvTacticalAI.h b/CvGameCoreDLL_Expansion2/CvTacticalAI.h index babdb4c0ec..658feca752 100644 --- a/CvGameCoreDLL_Expansion2/CvTacticalAI.h +++ b/CvGameCoreDLL_Expansion2/CvTacticalAI.h @@ -333,6 +333,7 @@ class CvTacticalAI // Public turn update routines void Update(); + void CleanUp(); // temporary focus of attention void AddFocusArea(CvPlot* pPlot, int iRadius, int iDuration); @@ -340,6 +341,12 @@ class CvTacticalAI void DropOldFocusAreas(); bool IsInFocusArea(const CvPlot* pPlot) const; + // Knowledge of other civs' vision + void UpdateVisibility(); + void NewVisiblePlot(CvPlot* pPlot, bool bRevealed); + bool IsVisibleToPlayer(const CvPlot* pPlot, TeamTypes eOther); + bool IsVisibleToEnemy(const CvPlot* pPlot); + // For air units bool ShouldRebase(CvUnit* pUnit) const; @@ -466,6 +473,7 @@ class CvTacticalAI bool FindEmbarkedUnitsAroundTarget(CvPlot *pTargetPlot, int iMaxDistance); bool FindCitiesWithinStrikingDistance(CvPlot* pTargetPlot); CvPlot* FindAirTargetNearTarget(CvUnit* pUnit, CvPlot* pTargetPlot); + void UpdateVisibilityFromBorders(CvPlot* pPlot, bool bRevealed); int GetRecruitRange() const; @@ -512,6 +520,9 @@ class CvTacticalAI int m_iCurrentTargetIndex; int m_iCurrentUnitTargetIndex; + // Visibility info + std::map> m_plotsVisibleToOtherPlayer; + std::vector m_focusAreas; };