Hooks
static bool BeforeTacticalAnalysisMapPathValid(CvAStarNode* parent, CvAStarNode* node, int data, const void* pointer, CvAStar* finder, int& result);static void AfterTacticalAnalysisMapPathValid(CvAStarNode* parent, CvAStarNode* node, int data, const void* pointer, CvAStar* finder, int& result);static int ReplaceTacticalAnalysisMapPathValid(int (*original)(CvAStarNode*, CvAStarNode*, int, const void*, CvAStar*), CvAStarNode* parent, CvAStarNode* node, int data, const void* pointer, CvAStar* finder);CivRegisterBefore<CivHooks::TacticalAnalysisMapPathValid>(host, &BeforeTacticalAnalysisMapPathValid);CivRegisterAfter<CivHooks::TacticalAnalysisMapPathValid>(host, &AfterTacticalAnalysisMapPathValid);CivRegisterReplace<CivHooks::TacticalAnalysisMapPathValid>(host, &ReplaceTacticalAnalysisMapPathValid);
Code
3395int TacticalAnalysisMapPathValid(CvAStarNode* parent, CvAStarNode* node, int data, const void* pointer, CvAStar* finder)3396{3397 CvMap& theMap = GC.getMap();33983399 CvPlot* pToPlot = theMap.plotUnchecked(node->m_iX, node->m_iY);3400 PREFETCH_FASTAR_CVPLOT(reinterpret_cast<char*>(pToPlot));34013402 CvUnit* pUnit = ((CvUnit *)pointer);3403 const UnitPathCacheData* pCacheData = reinterpret_cast<const UnitPathCacheData*>(finder->GetScratchBuffer());34043405 TeamTypes eUnitTeam = pCacheData->getTeam();3406 PlayerTypes unit_owner = pCacheData->getOwner();3407 CvAssertMsg(eUnitTeam != NO_TEAM, "The unit's team should be a vaild value");3408 if (eUnitTeam == NO_TEAM)3409 {3410 eUnitTeam = GC.getGame().getActiveTeam();3411 }34123413 CvTeam& kUnitTeam = GET_TEAM(eUnitTeam);34143415 CvTacticalAnalysisMap* pTAMap = GC.getGame().GetTacticalAnalysisMap();3416 FAssert(pTAMap != NULL);3417 CvTacticalAnalysisCell* pToPlotCell = pTAMap->GetCell(pToPlot->GetPlotIndex());3418 FAssert(pToPlotCell != NULL);34193420 // Cache the data for the node3421 CvPathNodeCacheData& kToNodeCacheData = node->m_kCostCacheData;3422 kToNodeCacheData.bPlotVisibleToTeam = pToPlotCell->IsVisible();3423 kToNodeCacheData.iNumFriendlyUnitsOfType = pToPlot->getNumFriendlyUnitsOfType(pUnit);3424#if defined(MOD_GLOBAL_STACKING_RULES)3425 kToNodeCacheData.iUnitPlotLimit = pToPlot->getUnitLimit();3426#endif3427 kToNodeCacheData.bIsMountain = pToPlot->isMountain();3428 kToNodeCacheData.bIsWater = pToPlotCell->IsWater();3429#if defined(MOD_PATHFINDER_TERRAFIRMA)3430 kToNodeCacheData.bIsTerraFirma = pToPlotCell->IsTerraFirma();3431#endif3432 kToNodeCacheData.bCanEnterTerrain = pUnit->canEnterTerrain(*pToPlot, CvUnit::MOVEFLAG_PRETEND_CORRECT_EMBARK_STATE);3433 kToNodeCacheData.bIsRevealedToTeam = pToPlotCell->IsRevealed();3434 kToNodeCacheData.bContainsOtherFriendlyTeamCity = false;3435 if(pToPlotCell->IsCity())3436 {3437 CvCity* pCity = pToPlot->getPlotCity();3438 if(pCity)3439 {3440 if(unit_owner != pCity->getOwner() && !kUnitTeam.isAtWar(pCity->getTeam()))3441 kToNodeCacheData.bContainsOtherFriendlyTeamCity = true;3442 }3443 }3444 kToNodeCacheData.bContainsEnemyCity = pToPlotCell->IsEnemyCity();3445 kToNodeCacheData.bContainsVisibleEnemy = pToPlotCell->GetEnemyMilitaryUnit() != NULL;3446 kToNodeCacheData.bContainsVisibleEnemyDefender = pToPlot->getBestDefender(NO_PLAYER, unit_owner, pUnit).pointer() != NULL;34473448 // If this is the first node in the path, it is always valid (starting location)3449 if (parent == NULL)3450 {3451 return TRUE;3452 }34533454 CvPlot* pFromPlot = theMap.plotUnchecked(parent->m_iX, parent->m_iY);3455 PREFETCH_FASTAR_CVPLOT(reinterpret_cast<char*>(pFromPlot));34563457 CvTacticalAnalysisCell* pFromPlotCell = pTAMap->GetCell(pFromPlot->GetPlotIndex());3458 FAssert(pFromPlotCell != NULL);34593460 bool bAIControl = pUnit->IsAutomated();34613462 // pulling invariants out of the loop3463 int iUnitX = pUnit->getX();3464 int iUnitY = pUnit->getY();3465 DomainTypes unit_domain_type = pCacheData->getDomainType();3466 bool bUnitIsCombat = pUnit->IsCombatUnit();3467 bool bIsHuman = pCacheData->isHuman();3468 int iFinderInfo = finder->GetInfo();3469 CvPlot* pUnitPlot = pUnit->plot();3470 int iFinderIgnoreStacking = iFinderInfo & MOVE_IGNORE_STACKING;3471#if !defined(MOD_GLOBAL_STACKING_RULES)3472 int iUnitPlotLimit = GC.getPLOT_UNIT_LIMIT();3473#endif3474 bool bFromPlotOwned = !pFromPlotCell->IsUnclaimedTerritory();3475 TeamTypes eFromPlotTeam = pFromPlot->getTeam();34763477 // We have determined that this node is not the origin above (parent == NULL)3478 CvAStarNode* pNode = node;3479 bool bPreviousNodeHostile = false;3480 int iDestX = finder->GetDestX();3481 int iDestY = finder->GetDestY();3482 int iNodeX = node->m_iX;3483 int iNodeY = node->m_iY;3484 int iOldNumTurns = -1;3485 int iNumTurns;3486 TeamTypes eTeam = eUnitTeam; // this may get modified later is eTEam == NO_TEAM34873488 // First run special case for checking "node" since it doesn't have a parent set yet3489 bool bFirstRun = true;34903491 // Have to calculate this specially because the node passed into this function doesn't yet have data stored it in (hasn't reached pathAdd yet)3492 int iStartMoves = parent->m_iData1;3493 iNumTurns = parent->m_iData2;34943495 if(iStartMoves == 0)3496 {3497 iNumTurns++;3498 }34993500 iOldNumTurns = -1;35013502 CvPlot* pPlot = NULL;35033504 // Get a reference to the parent node cache data3505 CvPathNodeCacheData& kFromNodeCacheData = parent->m_kCostCacheData;35063507 // Loop through the current path until we find the path origin.3508 // This validates the path with the inclusion of the new path node. We must do this because of the rules of where a unit can finish a turn.3509 // Please note that this can be an expensive loop as the path gets longer and longer, do as little work as possible in validating each node. 3510 // If there is an invariant value that needs to be fetched from the plot or unit for the node, please do the calculation and put it in the node's data cache.3511 while(pNode != NULL)3512 {3513 PREFETCH_FASTAR_NODE(pNode->m_pParent);35143515 CvPathNodeCacheData& kNodeCacheData = pNode->m_kCostCacheData;3516 // This is a safeguard against the algorithm believing a plot to be impassable before actually knowing it (mid-search)3517 if(iOldNumTurns != -1 || (iDestX == iNodeX && iDestY == iNodeY))3518 {3519 // This plot is of greater distance than previously, so we know the unit is ending its turn here (pNode), or it's trying to attack through a unit (and might end up on this tile if an attack fails to kill the enemy)3520 if(iNumTurns != iOldNumTurns || bPreviousNodeHostile)3521 {3522 // Don't count origin, or else a unit will block its own movement!3523 if(iNodeX != iUnitX || iNodeY != iUnitY)3524 {3525 // PREFETCH_FASTAR_CVPLOT(reinterpret_cast<char*>(pPlot));3526 if(kNodeCacheData.bPlotVisibleToTeam)3527 {3528 // Check to see if any units are present at this full-turn move plot... if the player can see what's there3529#if defined(MOD_GLOBAL_STACKING_RULES)3530 if(kNodeCacheData.iNumFriendlyUnitsOfType >= kNodeCacheData.iUnitPlotLimit && !(iFinderIgnoreStacking))3531#else3532 if(kNodeCacheData.iNumFriendlyUnitsOfType >= iUnitPlotLimit && !(iFinderIgnoreStacking))3533#endif3534 {3535 return FALSE;3536 }35373538 if (kNodeCacheData.bIsMountain && !(iFinderIgnoreStacking) && (!bIsHuman || bAIControl))3539 {3540 return FALSE;3541 }35423543 if(kNodeCacheData.bIsMountain && !kNodeCacheData.bCanEnterTerrain) // only doing canEnterTerrain on mountain plots because it is expensive, though it probably should always be called and some other checks in this loop could be removed.3544 {3545 return FALSE;3546 }35473548 if ((finder->GetInfo() & CvUnit::MOVEFLAG_STAY_ON_LAND) && kNodeCacheData.bIsWater)3549 {3550 return FALSE;3551 }3552 }35533554#if defined(MOD_GLOBAL_BREAK_CIVILIAN_RESTRICTIONS)3555 if(kNodeCacheData.bIsRevealedToTeam && (!MOD_GLOBAL_BREAK_CIVILIAN_RESTRICTIONS || bUnitIsCombat))3556#else3557 if(kNodeCacheData.bIsRevealedToTeam)3558#endif3559 {3560 if (kNodeCacheData.bContainsOtherFriendlyTeamCity && !(iFinderIgnoreStacking))3561 return FALSE;3562 }3563 }3564 }3565 }35663567 bPreviousNodeHostile = false;3568 if(kNodeCacheData.bContainsEnemyCity)3569 {3570 bPreviousNodeHostile = true;3571 }3572 // Prevents units from passing through one another on its way to attack another unit3573 else if(kNodeCacheData.bContainsVisibleEnemy)3574 {3575 // except when attacking an unguarded civilian unit3576 if(kNodeCacheData.bContainsVisibleEnemyDefender)3577 {3578 bPreviousNodeHostile = true;3579 }3580 }35813582 // JON - Special case for the original node passed into this function because it's not yet linked to any parent3583 if(pNode == node && bFirstRun)3584 {3585 pNode = parent;3586 bFirstRun = false;3587 }3588 else3589 {3590 pNode = pNode->m_pParent;3591 }35923593 if(pNode != NULL)3594 {35953596 iNodeX = pNode->m_iX;3597 iNodeY = pNode->m_iY;3598 iOldNumTurns = iNumTurns;3599 iNumTurns = pNode->m_iData2;3600 }3601 }36023603 // slewis - moved this up so units can't move directly into the water. Not 100% sure this is the right solution.3604 if(unit_domain_type == DOMAIN_LAND)3605 {3606#if defined(MOD_PATHFINDER_TERRAFIRMA)3607 bool bFromTerraFirma = kFromNodeCacheData.bIsTerraFirma;3608 bool bToWater = !kToNodeCacheData.bIsTerraFirma;3609 if(bFromTerraFirma && bToWater && kToNodeCacheData.bIsRevealedToTeam && !pUnit->canEmbarkOnto(*pFromPlot, *pToPlot, true))3610#else3611 if(!kFromNodeCacheData.bIsWater && kToNodeCacheData.bIsWater && kToNodeCacheData.bIsRevealedToTeam && !pUnit->canEmbarkOnto(*pFromPlot, *pToPlot, true))3612#endif3613 {3614#if defined(MOD_PATHFINDER_DEEP_WATER_EMBARKATION)3615 if(!pUnit->canMoveAllTerrain() && !pToPlot->IsAllowsWalkWater())3616#else3617 if(!pUnit->IsHoveringUnit() && !pUnit->canMoveAllTerrain() && !pToPlot->IsAllowsWalkWater())3618#endif3619 {3620 return FALSE;3621 }3622 }3623 }36243625#if defined(MOD_GLOBAL_BREAK_CIVILIAN_RESTRICTIONS)3626 if (!MOD_GLOBAL_BREAK_CIVILIAN_RESTRICTIONS) {3627#endif3628 if(!bUnitIsCombat && unit_domain_type != DOMAIN_AIR)3629 {3630 const PlayerTypes eUnitPlayer = unit_owner;3631 const int iUnitCount = pToPlot->getNumUnits();3632 for(int iUnit = 0; iUnit < iUnitCount; ++iUnit)3633 {3634 const CvUnit* pToPlotUnit = pToPlot->getUnitByIndex(iUnit);3635 if(pToPlotUnit != NULL && pToPlotUnit->getOwner() != eUnitPlayer)3636 {3637 return FALSE; // Plot occupied by another player3638 }3639 }3640 }3641#if defined(MOD_GLOBAL_BREAK_CIVILIAN_RESTRICTIONS)3642 }3643#endif36443645 // slewis - Added to catch when the unit is adjacent to an enemy unit while it is stacked with a friendly unit.3646 // The logic above (with bPreviousNodeHostile) catches this problem with a path that's longer than one step3647 // but does not catch when the path is only one step.3648 if(bUnitIsCombat && unit_domain_type != DOMAIN_AIR && pUnitPlot->isAdjacent(pToPlot) && kToNodeCacheData.bContainsVisibleEnemy && !(iFinderIgnoreStacking))3649 {3650 if(kToNodeCacheData.bContainsVisibleEnemyDefender)3651 {3652#if defined(MOD_GLOBAL_STACKING_RULES)3653 if(pUnitPlot->getNumFriendlyUnitsOfType(pUnit) > pUnitPlot->getUnitLimit())3654#else3655 if(pUnitPlot->getNumFriendlyUnitsOfType(pUnit) > iUnitPlotLimit)3656#endif3657 {3658 return FALSE;3659 }3660 }3661 }36623663 if(pUnitPlot == pFromPlot)3664 {3665 return TRUE;3666 }36673668 if(iFinderInfo & MOVE_TERRITORY_NO_UNEXPLORED)3669 {3670 if(!kFromNodeCacheData.bIsRevealedToTeam)3671 {3672 return FALSE;3673 }36743675 if(bFromPlotOwned)3676 {3677 if(eFromPlotTeam != eUnitTeam)3678 {3679 return FALSE;3680 }3681 }3682 }36833684 if(iFinderInfo & MOVE_TERRITORY_NO_ENEMY)3685 {3686 if(bFromPlotOwned)3687 {3688 if(atWar(eFromPlotTeam, eUnitTeam))3689 {3690 return FALSE;3691 }3692 }3693 }36943695 if(bAIControl)3696 {3697 if((parent->m_iData2 > 1) || (parent->m_iData1 == 0))3698 {3699 if(!(iFinderInfo & MOVE_UNITS_IGNORE_DANGER))3700 {3701 if(!bUnitIsCombat || pUnit->getArmyID() == FFreeList::INVALID_INDEX)3702 {3703 if(GET_PLAYER(unit_owner).GetPlotDanger(*pFromPlot) > 0)3704 {3705 return FALSE;3706 }3707 }3708 }3709 }3710 }37113712 // slewis - added AI check and embark check to prevent units from moving into unexplored areas3713 if(bAIControl || !bIsHuman || kFromNodeCacheData.bIsRevealedToTeam || pCacheData->isEmbarked())3714 {3715 if(iFinderInfo & MOVE_UNITS_THROUGH_ENEMY)3716 {3717 if(!(pUnit->canMoveOrAttackInto(*pFromPlot, CvUnit::MOVEFLAG_PRETEND_CORRECT_EMBARK_STATE)))3718 {3719 return FALSE;3720 }3721 }3722 else3723 {3724 if(!(pUnit->canMoveThrough(*pFromPlot, CvUnit::MOVEFLAG_PRETEND_CORRECT_EMBARK_STATE)))3725 {3726 return FALSE;3727 }3728 }3729 }37303731 return TRUE;3732}