/// Add a unit to our army (and we know which slot) void CvArmyAI::AddUnit(int iUnitID, int iSlotNum) { CvAssertMsg(iUnitID != ARMYSLOT_NO_UNIT,"Expect unit to be non-NULL"); CvPlayer& thisPlayer = GET_PLAYER(m_eOwner); UnitHandle pThisUnit = thisPlayer.getUnit(iUnitID); // remove this unit from an army if it is already in one thisPlayer.removeFromArmy(pThisUnit->getArmyID(), GetID()); m_FormationEntries[iSlotNum].SetUnitID(iUnitID); pThisUnit->setArmyID(GetID()); #if defined(MOD_BALANCE_CORE) //just for avoiding confusion pThisUnit->setTacticalMove(NO_TACTICAL_MOVE); #endif // Finally, compute when we think this unit will arrive at the next checkpoint CvPlot* pMusterPlot = GC.getMap().plot(GetX(), GetY()); if(pMusterPlot) { int iFlags = CvUnit::MOVEFLAG_APPROXIMATE_TARGET | CvUnit::MOVEFLAG_IGNORE_STACKING | CvUnit::MOVEFLAG_IGNORE_ZOC; int iTurnsToReachCheckpoint = pThisUnit->TurnsToReachTarget(pMusterPlot, iFlags, GC.getAI_OPERATIONAL_MAX_RECRUIT_TURNS_ENEMY_TERRITORY()); if(iTurnsToReachCheckpoint < MAX_INT) { SetEstimatedTurn(iSlotNum, iTurnsToReachCheckpoint); } } }
/// Remove a unit from the army bool CvArmyAI::RemoveUnit(int iUnitToRemoveID) { bool bWasOneOrMoreRemoved = false; CvArmyFormationSlot slot; for(int iI = 0; iI < (int)m_FormationEntries.size(); iI++) { slot = m_FormationEntries[iI]; if(slot.GetUnitID() == iUnitToRemoveID) { UnitHandle pThisUnit = GET_PLAYER(GetOwner()).getUnit(iUnitToRemoveID); if(pThisUnit) { // Clears unit's army ID and erase from formation entries pThisUnit->setArmyID(FFreeList::INVALID_INDEX); m_FormationEntries[iI].SetUnitID(ARMY_NO_UNIT); bWasOneOrMoreRemoved = true; // Tell the associate operation that a unit was lost CvAIOperation* pThisOperation = GET_PLAYER(GetOwner()).getAIOperation(m_iOperationID); if(pThisOperation) { pThisOperation->UnitWasRemoved(GetID(), iI); } } } } return bWasOneOrMoreRemoved; }
/// Remove a unit from the army bool CvArmyAI::RemoveUnit(int iUnitToRemoveID) { for(int iI = 0; iI < (int)m_FormationEntries.size(); iI++) { CvArmyFormationSlot slot = m_FormationEntries[iI]; if(slot.GetUnitID() == iUnitToRemoveID) { UnitHandle pThisUnit = GET_PLAYER(GetOwner()).getUnit(iUnitToRemoveID); if(pThisUnit) { // Clears unit's army ID and erase from formation entries pThisUnit->setArmyID(-1); pThisUnit->AI_setUnitAIType(pThisUnit->getUnitInfo().GetDefaultUnitAIType()); m_FormationEntries[iI].SetUnitID(ARMYSLOT_NO_UNIT); // Tell the associate operation that a unit was lost CvAIOperation* pThisOperation = GET_PLAYER(GetOwner()).getAIOperation(m_iOperationID); if(pThisOperation) { pThisOperation->UnitWasRemoved(GetID(), iI); } return true; } } } return false; }
CvPlot* CvArmyAI::DetectNearbyEnemy(PlayerTypes eEnemy, bool bNaval) { UnitHandle pUnit = GetFirstUnit(); while(pUnit) { for(int iDirectionLoop = 0; iDirectionLoop < NUM_DIRECTION_TYPES; ++iDirectionLoop) { CvPlot* pAdjacentPlot = plotDirection(pUnit->getX(), pUnit->getY(), ((DirectionTypes)iDirectionLoop)); if(pAdjacentPlot != NULL && pAdjacentPlot->isWater()==bNaval && pAdjacentPlot->getOwner() == eEnemy) { UnitHandle pOtherUnit = pAdjacentPlot->getBestDefender(eEnemy); if(pOtherUnit) { if(GC.getLogging() && GC.getAILogging()) { CvString strMsg; strMsg.Format("Ran into enemy unit during attack (x=%d y=%d). Need to declare war to continue!", pAdjacentPlot->getX(), pAdjacentPlot->getY()); GET_PLAYER(m_eOwner).getAIOperation(m_iOperationID)->LogOperationSpecialMessage(strMsg); } return pAdjacentPlot; } } } pUnit = GetNextUnit(); } return NULL; }
/// Remove a unit from the army bool CvArmyAI::RemoveUnit(int iUnitToRemoveID) { bool bWasOneOrMoreRemoved = false; CvArmyFormationSlot slot; for(int iI = 0; iI < (int)m_FormationEntries.size(); iI++) { slot = m_FormationEntries[iI]; if(slot.GetUnitID() == iUnitToRemoveID) { UnitHandle pThisUnit = GET_PLAYER(GetOwner()).getUnit(iUnitToRemoveID); if(pThisUnit) { // Clears unit's army ID and erase from formation entries pThisUnit->setArmyID(-1); #if defined(MOD_BALANCE_CORE) pThisUnit->AI_setUnitAIType(pThisUnit->getUnitInfo().GetDefaultUnitAIType()); #endif m_FormationEntries[iI].SetUnitID(ARMY_NO_UNIT); bWasOneOrMoreRemoved = true; // Tell the associate operation that a unit was lost CvAIOperation* pThisOperation = GET_PLAYER(GetOwner()).getAIOperation(m_iOperationID); if(pThisOperation) { pThisOperation->UnitWasRemoved(GetID(), iI); } } } } return bWasOneOrMoreRemoved; }
/// Delete the army void CvArmyAI::Kill() { CvAssert(GetOwner() != NO_PLAYER); CvAssertMsg(GetID() != -1, "GetID() is not expected to be equal with -1"); int iUnitID; iUnitID = GetFirstUnitID(); while(iUnitID != ARMYSLOT_NO_UNIT) { UnitHandle pThisUnit = GET_PLAYER(GetOwner()).getUnit(iUnitID); if(pThisUnit) { pThisUnit->setArmyID(-1); #if defined(MOD_BALANCE_CORE) pThisUnit->AI_setUnitAIType(pThisUnit->getUnitInfo().GetDefaultUnitAIType()); #endif } iUnitID = GetNextUnitID(); } m_FormationEntries.clear(); CvAIOperation* pOperation = GET_PLAYER(GetOwner()).getAIOperation(m_iOperationID); if (pOperation) pOperation->DeleteArmyAI(m_iID); GET_PLAYER(GetOwner()).deleteArmyAI(m_iID); }
/// Everyone in the water now? bool CvArmyAI::AreAllInWater() { UnitHandle pUnit; pUnit = GetFirstUnit(); while(pUnit) { if(!pUnit->plot()->isWater()) { return false; } pUnit = GetNextUnit(); } return true; }
/// Return distance from this plot of unit in army farthest away int CvArmyAI::GetFurthestUnitDistance(CvPlot* pPlot) { int iLargestDistance = 0; UnitHandle pUnit; int iNewDistance; pUnit = GetFirstUnit(); while(pUnit) { iNewDistance = plotDistance(pUnit->getX(), pUnit->getY(), pPlot->getX(), pPlot->getY()); if(iNewDistance > iLargestDistance) { iLargestDistance = iNewDistance; } pUnit = GetNextUnit(); } return iLargestDistance; }
// UNIT STRENGTH ACCESSORS /// Total unit power int CvArmyAI::GetTotalPower() { int iRtnValue = 0; int iUnitID; iUnitID = GetFirstUnitID(); while(iUnitID != ARMYSLOT_NO_UNIT) { UnitHandle pThisUnit = GET_PLAYER(GetOwner()).getUnit(iUnitID); if(pThisUnit) { iRtnValue += pThisUnit->GetPower(); } iUnitID = GetNextUnitID(); } return iRtnValue; }
/// Delete the army void CvArmyAI::Kill() { CvAssert(GetOwner() != NO_PLAYER); CvAssertMsg(GetID() != FFreeList::INVALID_INDEX, "GetID() is not expected to be equal with FFreeList::INVALID_INDEX"); int iUnitID; iUnitID = GetFirstUnitID(); while(iUnitID != ARMY_NO_UNIT) { UnitHandle pThisUnit = GET_PLAYER(GetOwner()).getUnit(iUnitID); if(pThisUnit) { pThisUnit->setArmyID(FFreeList::INVALID_INDEX); } iUnitID = GetNextUnitID(); } m_FormationEntries.clear(); }
/// Find average speed of units in army int CvArmyAI::GetMovementRate() { int iMovementAverage = 2; // A reasonable default int iNumUnits = 0; int iTotalMovementAllowance = 0; UnitHandle pUnit; pUnit = GetFirstUnit(); while(pUnit) { iNumUnits++; iTotalMovementAllowance += pUnit->baseMoves(); pUnit = GetNextUnit(); } if(iNumUnits > 0) { iMovementAverage = (iTotalMovementAllowance + (iNumUnits / 2)) / iNumUnits; } return iMovementAverage; }
/// Can all units in this army move on ocean? bool CvArmyAI::IsAllOceanGoing() { UnitHandle pUnit; pUnit = GetFirstUnit(); while(pUnit) { if(pUnit->getDomainType() != DOMAIN_SEA && !pUnit->IsHasEmbarkAbility()) { return false; } // If can move over ocean, not a coastal vessel if(pUnit->isTerrainImpassable(TERRAIN_OCEAN)) { return false; } pUnit = GetNextUnit(); } return true; }
/// Add a unit to our army (and we know which slot) void CvArmyAI::AddUnit(int iUnitID, int iSlotNum) { CvAssertMsg(iUnitID != ARMY_NO_UNIT,"Expect unit to be non-NULL"); CvPlayer& thisPlayer = GET_PLAYER(m_eOwner); UnitHandle pThisUnit = thisPlayer.getUnit(iUnitID); // remove this unit from an army if it is already in one thisPlayer.removeFromArmy(pThisUnit->getArmyID(), GetID()); m_FormationEntries[iSlotNum].SetUnitID(iUnitID); pThisUnit->setArmyID(GetID()); // Finally, compute when we think this unit will arrive at the next checkpoint CvPlot* pMusterPlot = GC.getMap().plot(GetX(), GetY()); if(pMusterPlot) { int iTurnsToReachCheckpoint = TurnsToReachTarget(pThisUnit, pMusterPlot, true /*bReusePaths*/, true, true); if(iTurnsToReachCheckpoint < MAX_INT) { SetEstimatedTurn(iSlotNum, iTurnsToReachCheckpoint); } } }
/// Get center of mass of units in army (account for world wrap!) CvPlot* CvArmyAI::GetCenterOfMass(DomainTypes eDomainRequired) { int iTotalX = 0; int iTotalY = 0; int iNumUnits = 0; #if defined(MOD_BALANCE_CORE) UnitHandle pUnit = GetFirstUnit(); if (!pUnit) return NULL; int iTotalX2 = 0; int iTotalY2 = 0; int iWorldWidth = GC.getMap().getGridWidth(); int iWorldHeight = GC.getMap().getGridHeight(); //the first unit is our reference ... int iRefX = pUnit->getX(); int iRefY = pUnit->getY(); iNumUnits++; pUnit = GetNextUnit(); while(pUnit) { int iDX = pUnit->getX() - iRefX; int iDY = pUnit->getY() - iRefY; if (GC.getMap().isWrapX()) { if( iDX > +(iWorldWidth / 2)) iDX -= iWorldWidth; if( iDX < -(iWorldWidth / 2)) iDX += iWorldWidth; } if (GC.getMap().isWrapY()) { if( iDY > +(iWorldHeight / 2)) iDY -= iWorldHeight; if( iDY < -(iWorldHeight / 2)) iDY += iWorldHeight; } iTotalX += iDX; iTotalY += iDY; iTotalX2 += iDX*iDX; iTotalY2 += iDY*iDY; iNumUnits++; pUnit = GetNextUnit(); } if (iNumUnits==0) return NULL; //this is for debugging float fVarX = (iTotalX2 / (float)iNumUnits) - (iTotalX/(float)iNumUnits)*(iTotalX/(float)iNumUnits); float fVarY = (iTotalY2 / (float)iNumUnits) - (iTotalY/(float)iNumUnits)*(iTotalY/(float)iNumUnits); //finally, compute average (with rounding) int iAvgX = (iTotalX + (iNumUnits / 2)) / iNumUnits + iRefX; int iAvgY = (iTotalY + (iNumUnits / 2)) / iNumUnits + iRefY; if (fVarX > 64 || fVarY > 64) { CvString msg = CvString::format("Warning: Army %d with %d units Center of Mass (%d,%d) has a large variance (%.2f,%.2f)\n", GetID(), iNumUnits, iAvgX, iAvgY, fVarX, fVarY); OutputDebugString( msg.c_str() ); } //this handles wrapped coordinates CvPlot* pCOM = GC.getMap().plot(iAvgX, iAvgY); if (!pCOM) return NULL; //don't return it directly but use the plot of the closest unit pUnit = GetFirstUnit(); std::vector<SPlotWithScore> vPlots; while (pUnit) { if (eDomainRequired == NO_DOMAIN || pUnit->plot()->getDomain()==eDomainRequired) vPlots.push_back( SPlotWithScore(pUnit->plot(),plotDistance(*pUnit->plot(),*pCOM)) ); pUnit = GetNextUnit(); } if (vPlots.empty()) return NULL; //this sorts ascending! std::sort(vPlots.begin(),vPlots.end()); return vPlots.front().pPlot; #else CvPlot* pRtnValue = NULL; UnitHandle pUnit; int iReferenceUnitX = -1; int iWorldWidth = GC.getMap().getGridWidth(); pUnit = GetFirstUnit(); if(pUnit) { iReferenceUnitX = pUnit->getX(); } while(pUnit) { int iUnitX = pUnit->getX(); bool bWorldWrapAdjust = false; int iDiff = iUnitX - iReferenceUnitX; if(abs(iDiff) > (iWorldWidth / 2)) { bWorldWrapAdjust = true; } if(bWorldWrapAdjust) { iTotalX += iUnitX + iWorldWidth; } else { iTotalX += iUnitX; } iTotalY += pUnit->getY(); iNumUnits++; pUnit = GetNextUnit(); } if(iNumUnits > 0) { int iAverageX = (iTotalX + (iNumUnits / 2)) / iNumUnits; if(iAverageX >= iWorldWidth) { iAverageX = iAverageX - iWorldWidth; } int iAverageY = (iTotalY + (iNumUnits / 2)) / iNumUnits; pRtnValue = GC.getMap().plot(iAverageX, iAverageY); } // Domain check if (eDomainRequired != NO_DOMAIN && pRtnValue) { if (pRtnValue->isWater() && eDomainRequired == DOMAIN_LAND || !pRtnValue->isWater() && eDomainRequired == DOMAIN_SEA) { // Find an adjacent plot that works for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++) { CvPlot *pLoopPlot = plotDirection(pRtnValue->getX(), pRtnValue->getY(), ((DirectionTypes)iI)); if (pLoopPlot != NULL) { if (pLoopPlot->isWater() && eDomainRequired == DOMAIN_SEA || !pLoopPlot->isWater() && eDomainRequired == DOMAIN_LAND) { return pLoopPlot; } } } // Try two plots out if really having problems for (int iDX = -2; iDX <= 2; iDX++) { for (int iDY = -2; iDY <= 2; iDY++) { CvPlot *pLoopPlot = plotXYWithRangeCheck(pRtnValue->getX(), pRtnValue->getY(), iDX, iDY, 2); if (pLoopPlot) { if (plotDistance(pRtnValue->getX(), pRtnValue->getY(), pLoopPlot->getX(), pLoopPlot->getY()) == 2) { if (pLoopPlot->isWater() && eDomainRequired == DOMAIN_SEA || !pLoopPlot->isWater() && eDomainRequired == DOMAIN_LAND) { return pLoopPlot; } } } } } // Give up - just use location of first unit pUnit = GetFirstUnit(); pRtnValue = pUnit->plot(); } } return pRtnValue; #endif }
/// Updates the danger plots values to reflect threats across the map void CvDangerPlots::UpdateDanger(bool bPretendWarWithAllCivs, bool bIgnoreVisibility) { // danger plots have not been initialized yet, so no need to update if(!m_bArrayAllocated) return; // wipe out values int iGridSize = GC.getMap().numPlots(); CvAssertMsg(iGridSize == m_DangerPlots.size(), "iGridSize does not match number of DangerPlots"); for(int i = 0; i < iGridSize; i++) { m_DangerPlots[i].clear(); } //units we know from last turn UnitSet previousKnownUnits = m_knownUnits; m_knownUnits.clear(); CvPlayer& thisPlayer = GET_PLAYER(m_ePlayer); TeamTypes thisTeam = thisPlayer.getTeam(); // for each opposing civ for(int iPlayer = 0; iPlayer < MAX_PLAYERS; iPlayer++) { PlayerTypes ePlayer = (PlayerTypes)iPlayer; CvPlayer& loopPlayer = GET_PLAYER(ePlayer); TeamTypes eTeam = loopPlayer.getTeam(); if(!loopPlayer.isAlive()) continue; if(eTeam == thisTeam) continue; if(ShouldIgnorePlayer(ePlayer) && !bPretendWarWithAllCivs) continue; //for each unit int iLoop; CvUnit* pLoopUnit = NULL; for(pLoopUnit = loopPlayer.firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = loopPlayer.nextUnit(&iLoop)) { UpdateDangerSingleUnit(pLoopUnit, bIgnoreVisibility, true); } // for each city CvCity* pLoopCity; for(pLoopCity = loopPlayer.firstCity(&iLoop); pLoopCity != NULL; pLoopCity = loopPlayer.nextCity(&iLoop)) { if(ShouldIgnoreCity(pLoopCity, bIgnoreVisibility)) continue; #if defined(MOD_EVENTS_CITY_BOMBARD) bool bIndirectFireAllowed = false; int iRange = pLoopCity->getBombardRange(bIndirectFireAllowed); #else int iRange = GC.getCITY_ATTACK_RANGE(); #endif CvPlot* pCityPlot = pLoopCity->plot(); CvPlot* pLoopPlot = NULL; for(int iDX = -(iRange); iDX <= iRange; iDX++) { for(int iDY = -(iRange); iDY <= iRange; iDY++) { pLoopPlot = plotXYWithRangeCheck(pCityPlot->getX(), pCityPlot->getY(), iDX, iDY, iRange); if(!pLoopPlot || pLoopPlot == pCityPlot) continue; #if defined(MOD_EVENTS_CITY_BOMBARD) if (!bIndirectFireAllowed && !pCityPlot->canSeePlot(pLoopPlot, NO_TEAM, iRange, NO_DIRECTION)) continue; #endif AssignCityDangerValue(pLoopCity, pLoopPlot); } } } } // now compare the new known units with the previous known units for (UnitSet::iterator it = previousKnownUnits.begin(); it != previousKnownUnits.end(); ++it) { //might have made peace ... if (ShouldIgnorePlayer(it->first)) continue; if (m_knownUnits.find(*it) == m_knownUnits.end()) { CvUnit* pVanishedUnit = GET_PLAYER(it->first).getUnit(it->second); //it's still there, but moved out of sight - nevertheless count is, a human would do that as well //do not add it to the known units though, so next turn we will have forgotten about it if (pVanishedUnit) UpdateDangerSingleUnit(pVanishedUnit, true, false); } } int iPlotLoop; CvPlot* pPlot, *pAdjacentPlot; for(iPlotLoop = 0; iPlotLoop < GC.getMap().numPlots(); iPlotLoop++) { pPlot = GC.getMap().plotByIndexUnchecked(iPlotLoop); if(pPlot->isRevealed(thisTeam)) { //remember the plot based damage, but it depends on the unit's promotions also, so we won't apply it directly int iPlotDamage = 0; if (pPlot->getFeatureType() != NO_FEATURE) iPlotDamage += (GC.getFeatureInfo(pPlot->getFeatureType())->getTurnDamage()); if (pPlot->getTerrainType() != NO_FEATURE) iPlotDamage += (GC.getTerrainInfo(pPlot->getTerrainType())->getTurnDamage()); m_DangerPlots[iPlotLoop].m_bFlatPlotDamage = (iPlotDamage>0); ImprovementTypes eImprovement = pPlot->getRevealedImprovementType(thisTeam); if(eImprovement != NO_IMPROVEMENT && GC.getImprovementInfo(eImprovement)->GetNearbyEnemyDamage() > 0) { if(!ShouldIgnoreCitadel(pPlot, bIgnoreVisibility)) { for(int iI = 0; iI < NUM_DIRECTION_TYPES; iI++) { pAdjacentPlot = plotDirection(pPlot->getX(), pPlot->getY(), ((DirectionTypes)iI)); if(pAdjacentPlot != NULL) { m_DangerPlots[iPlotLoop].m_pCitadel = pPlot; } } } } } } // testing city danger values CvCity* pLoopCity; int iLoopCity = 0; for(pLoopCity = thisPlayer.firstCity(&iLoopCity); pLoopCity != NULL; pLoopCity = thisPlayer.nextCity(&iLoopCity)) { //adding danger would count each unit multiple times, is biased towards fast units //so we pretend they would all attack the city and tally up the damage //question is, what about our own defensive units in the area. should we count those as well? int iEvalRange = 4; int iThreatValue = 0; for(int iX = -iEvalRange; iX <= iEvalRange; iX++) for(int iY = -iEvalRange; iY <= iEvalRange; iY++) { CvPlot* pEvalPlot = plotXYWithRangeCheck(pLoopCity->getX(), pLoopCity->getY(), iX, iY, iEvalRange); if (pEvalPlot) { const UnitHandle pEnemy = pEvalPlot->getBestDefender(NO_PLAYER, thisPlayer.GetID(), NULL, true); if (pEnemy) { int iAttackerDamage = 0; //to be ignored iThreatValue += TacticalAIHelpers::GetSimulatedDamageFromAttackOnCity(pLoopCity,pEnemy.pointer(),iAttackerDamage); } } } pLoopCity->SetThreatValue(iThreatValue); } m_bDirty = false; }
/// Get center of mass of units in army (account for world wrap!) CvPlot* CvArmyAI::GetCenterOfMass(DomainTypes eDomainRequired) { CvPlot* pRtnValue = NULL; int iTotalX = 0; int iTotalY = 0; int iNumUnits = 0; UnitHandle pUnit; int iReferenceUnitX = -1; int iWorldWidth = GC.getMap().getGridWidth(); pUnit = GetFirstUnit(); if(pUnit) { iReferenceUnitX = pUnit->getX(); } while(pUnit) { int iUnitX = pUnit->getX(); bool bWorldWrapAdjust = false; int iDiff = iUnitX - iReferenceUnitX; if(abs(iDiff) > (iWorldWidth / 2)) { bWorldWrapAdjust = true; } if(bWorldWrapAdjust) { iTotalX += iUnitX + iWorldWidth; } else { iTotalX += iUnitX; } iTotalY += pUnit->getY(); iNumUnits++; pUnit = GetNextUnit(); } if(iNumUnits > 0) { int iAverageX = (iTotalX + (iNumUnits / 2)) / iNumUnits; if(iAverageX >= iWorldWidth) { iAverageX = iAverageX - iWorldWidth; } int iAverageY = (iTotalY + (iNumUnits / 2)) / iNumUnits; pRtnValue = GC.getMap().plot(iAverageX, iAverageY); } // Domain check if (eDomainRequired != NO_DOMAIN && pRtnValue) { if (pRtnValue->isWater() && eDomainRequired == DOMAIN_LAND || !pRtnValue->isWater() && eDomainRequired == DOMAIN_SEA) { // Find an adjacent plot that works for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++) { CvPlot *pLoopPlot = plotDirection(pRtnValue->getX(), pRtnValue->getY(), ((DirectionTypes)iI)); if (pLoopPlot != NULL) { if (pLoopPlot->isWater() && eDomainRequired == DOMAIN_SEA || !pLoopPlot->isWater() && eDomainRequired == DOMAIN_LAND) { return pLoopPlot; } } } // Try two plots out if really having problems #ifdef AUI_HEXSPACE_DX_LOOPS int iMaxDX, iDX; CvPlot* pLoopPlot; for (int iDY = -2; iDY <= 2; iDY++) { iMaxDX = 2 - MAX(0, iDY); for (iDX = -2 - MIN(0, iDY); iDX <= iMaxDX; iDX++) // MIN() and MAX() stuff is to reduce loops (hexspace!) { // No need for range check because loops are set up properly pLoopPlot = plotXY(pRtnValue->getX(), pRtnValue->getY(), iDX, iDY); if (pLoopPlot) { if (hexDistance(iDX, iDY) == 2) #else for (int iDX = -2; iDX <= 2; iDX++) { for (int iDY = -2; iDY <= 2; iDY++) { CvPlot *pLoopPlot = plotXYWithRangeCheck(pRtnValue->getX(), pRtnValue->getY(), iDX, iDY, 2); if (pLoopPlot) { #ifdef AUI_FIX_HEX_DISTANCE_INSTEAD_OF_PLOT_DISTANCE if (hexDistance(iDX, iDY) == 2) #else if (plotDistance(pRtnValue->getX(), pRtnValue->getY(), pLoopPlot->getX(), pLoopPlot->getY()) == 2) #endif #endif { if (pLoopPlot->isWater() && eDomainRequired == DOMAIN_SEA || !pLoopPlot->isWater() && eDomainRequired == DOMAIN_LAND) { return pLoopPlot; } } } } } // Give up - just use location of first unit pUnit = GetFirstUnit(); pRtnValue = pUnit->plot(); } } return pRtnValue; } /// Return distance from this plot of unit in army farthest away int CvArmyAI::GetFurthestUnitDistance(CvPlot* pPlot) { int iLargestDistance = 0; UnitHandle pUnit; int iNewDistance; pUnit = GetFirstUnit(); while(pUnit) { iNewDistance = plotDistance(pUnit->getX(), pUnit->getY(), pPlot->getX(), pPlot->getY()); if(iNewDistance > iLargestDistance) { iLargestDistance = iNewDistance; } pUnit = GetNextUnit(); } return iLargestDistance; } // FORMATION ACCESSORS /// Retrieve index of the formation used by this army int CvArmyAI::GetFormationIndex() const { return m_iFormationIndex; } /// Set index of the formation used by this army void CvArmyAI::SetFormationIndex(int iFormationIndex) { CvArmyFormationSlot slot; if(m_iFormationIndex != iFormationIndex) { m_iFormationIndex = iFormationIndex; CvMultiUnitFormationInfo* thisFormation = GC.getMultiUnitFormationInfo(m_iFormationIndex); if(thisFormation) { int iNumSlots = thisFormation->getNumFormationSlotEntries(); // Build all the formation entries m_FormationEntries.clear(); for(int iI = 0; iI < iNumSlots; iI++) { slot.SetUnitID(ARMY_NO_UNIT); slot.SetTurnAtCheckpoint(ARMYSLOT_UNKNOWN_TURN_AT_CHECKPOINT); m_FormationEntries.push_back(slot); } } } } /// How many slots are there in this formation if filled int CvArmyAI::GetNumFormationEntries() const { return m_FormationEntries.size(); } /// How many slots do we currently have filled? int CvArmyAI::GetNumSlotsFilled() const { int iRtnValue = 0; for(unsigned int iI = 0; iI < m_FormationEntries.size(); iI++) { if(m_FormationEntries[iI].m_iUnitID != ARMY_NO_UNIT) { iRtnValue++; } } return iRtnValue; }
/// Get center of mass of units in army (account for world wrap!) CvPlot* CvArmyAI::GetCenterOfMass(DomainTypes eDomainRequired) { CvPlot* pRtnValue = NULL; int iTotalX = 0; int iTotalY = 0; int iNumUnits = 0; UnitHandle pUnit; int iReferenceUnitX = -1; int iWorldWidth = GC.getMap().getGridWidth(); pUnit = GetFirstUnit(); if(pUnit) { iReferenceUnitX = pUnit->getX(); } while(pUnit) { int iUnitX = pUnit->getX(); bool bWorldWrapAdjust = false; int iDiff = iUnitX - iReferenceUnitX; if(abs(iDiff) > (iWorldWidth / 2)) { bWorldWrapAdjust = true; } if(bWorldWrapAdjust) { iTotalX += iUnitX + iWorldWidth; } else { iTotalX += iUnitX; } iTotalY += pUnit->getY(); iNumUnits++; pUnit = GetNextUnit(); } if(iNumUnits > 0) { int iAverageX = (iTotalX + (iNumUnits / 2)) / iNumUnits; if(iAverageX >= iWorldWidth) { iAverageX = iAverageX - iWorldWidth; } int iAverageY = (iTotalY + (iNumUnits / 2)) / iNumUnits; pRtnValue = GC.getMap().plot(iAverageX, iAverageY); } // Domain check if (eDomainRequired != NO_DOMAIN && pRtnValue) { if (pRtnValue->isWater() && eDomainRequired == DOMAIN_LAND || !pRtnValue->isWater() && eDomainRequired == DOMAIN_SEA) { // Find an adjacent plot that works for (int iI = 0; iI < NUM_DIRECTION_TYPES; iI++) { CvPlot *pLoopPlot = plotDirection(pRtnValue->getX(), pRtnValue->getY(), ((DirectionTypes)iI)); if (pLoopPlot != NULL) { if (pLoopPlot->isWater() && eDomainRequired == DOMAIN_SEA || !pLoopPlot->isWater() && eDomainRequired == DOMAIN_LAND) { return pLoopPlot; } } } // Try two plots out if really having problems for (int iDX = -2; iDX <= 2; iDX++) { for (int iDY = -2; iDY <= 2; iDY++) { CvPlot *pLoopPlot = plotXYWithRangeCheck(pRtnValue->getX(), pRtnValue->getY(), iDX, iDY, 2); if (pLoopPlot) { if (plotDistance(pRtnValue->getX(), pRtnValue->getY(), pLoopPlot->getX(), pLoopPlot->getY()) == 2) { if (pLoopPlot->isWater() && eDomainRequired == DOMAIN_SEA || !pLoopPlot->isWater() && eDomainRequired == DOMAIN_LAND) { return pLoopPlot; } } } } } // Give up - just use location of first unit pUnit = GetFirstUnit(); pRtnValue = pUnit->plot(); } } return pRtnValue; }
/// Get center of mass of units in army (account for world wrap!) CvPlot* CvArmyAI::GetCenterOfMass(float* pfVarX, float* pfVarY) { int iTotalX = 0; int iTotalY = 0; int iNumUnits = 0; UnitHandle pUnit = GetFirstUnit(); if (!pUnit) return NULL; int iTotalX2 = 0; int iTotalY2 = 0; int iWorldWidth = GC.getMap().getGridWidth(); int iWorldHeight = GC.getMap().getGridHeight(); //the first unit is our reference ... int iRefX = pUnit->getX(); int iRefY = pUnit->getY(); iNumUnits++; pUnit = GetNextUnit(); while(pUnit) { int iDX = pUnit->getX() - iRefX; int iDY = pUnit->getY() - iRefY; if (GC.getMap().isWrapX()) { if( iDX > +(iWorldWidth / 2)) iDX -= iWorldWidth; if( iDX < -(iWorldWidth / 2)) iDX += iWorldWidth; } if (GC.getMap().isWrapY()) { if( iDY > +(iWorldHeight / 2)) iDY -= iWorldHeight; if( iDY < -(iWorldHeight / 2)) iDY += iWorldHeight; } iTotalX += iDX; iTotalY += iDY; iTotalX2 += iDX*iDX; iTotalY2 += iDY*iDY; iNumUnits++; pUnit = GetNextUnit(); } if (iNumUnits==0) return NULL; //this is for debugging float fVarX = (iTotalX2 / (float)iNumUnits) - (iTotalX/(float)iNumUnits)*(iTotalX/(float)iNumUnits); float fVarY = (iTotalY2 / (float)iNumUnits) - (iTotalY/(float)iNumUnits)*(iTotalY/(float)iNumUnits); //finally, compute average (with rounding) int iAvgX = (iTotalX + (iNumUnits / 2)) / iNumUnits + iRefX; int iAvgY = (iTotalY + (iNumUnits / 2)) / iNumUnits + iRefY; if (fVarX > 64 || fVarY > 64) { CvString msg = CvString::format("Warning: Army %d with %d units Center of Mass (%d,%d) has a large variance (%.2f,%.2f)\n", GetID(), iNumUnits, iAvgX, iAvgY, fVarX, fVarY); OutputDebugString( msg.c_str() ); } //this handles wrapped coordinates CvPlot* pCOM = GC.getMap().plot(iAvgX, iAvgY); if (!pCOM) return NULL; if (pfVarX) *pfVarX = fVarX; if (pfVarY) *pfVarY = fVarY; //don't return it directly but use the plot of the closest unit pUnit = GetFirstUnit(); std::vector<SPlotWithScore> vPlots; while (pUnit) { if (pUnit->plot()->getDomain()==GetDomainType()) { int iDistToCOM = plotDistance(*pUnit->plot(),*pCOM); int iDistToTarget = plotDistance(pUnit->getX(),pUnit->getY(),GetGoalX(),GetGoalY()); vPlots.push_back( SPlotWithScore(pUnit->plot(),iDistToCOM*100+iDistToTarget) ); } pUnit = GetNextUnit(); } if (vPlots.empty()) return NULL; //this sorts ascending! std::sort(vPlots.begin(),vPlots.end()); return vPlots.front().pPlot; }