From f792eb2da55fb1e7631515d96a5149f8aa63895a Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Sun, 15 Sep 2024 11:22:37 +0200 Subject: [PATCH 01/14] Add time measurement for auto-battle --- client/NetPacksClient.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 9b6482b47..35a7f86b5 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -814,11 +814,21 @@ void ApplyClientNetPackVisitor::visitBattleNextRound(BattleNextRound & pack) callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleNewRound, pack.battleID); } +uint64_t timeElapsed(std::chrono::time_point start) +{ + auto end = std::chrono::high_resolution_clock::now(); + + return std::chrono::duration_cast(end - start).count(); +} + void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & pack) { if(!pack.askPlayerInterface) return; + auto start = std::chrono::steady_clock::now(); + static uint64_t duration = 0; + const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack); PlayerColor playerToCall; //pack.player that will move activated stack if(activated->isHypnotized()) @@ -833,6 +843,8 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & } cl.startPlayerBattleAction(pack.battleID, playerToCall); + duration += timeElapsed(start); + logGlobal->warn("Battle elapsed for %ld ms", duration); } void ApplyClientNetPackVisitor::visitBattleLogMessage(BattleLogMessage & pack) From a99274d72eff6926e79a447a922ba7b186a381d2 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Sat, 28 Sep 2024 20:50:26 +0200 Subject: [PATCH 02/14] BattleHexArray - new container for BattleHexes --- AI/BattleAI/AttackPossibility.cpp | 4 +- AI/BattleAI/BattleEvaluator.cpp | 12 +- AI/BattleAI/BattleEvaluator.h | 2 +- AI/BattleAI/BattleExchangeVariant.cpp | 23 +- AI/BattleAI/BattleExchangeVariant.h | 2 +- AI/Nullkiller/Helpers/ExplorationHelper.h | 98 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- .../Pathfinding/ObjectGraphCalculator.cpp | 28 +- .../Pathfinding/ObjectGraphCalculator.h | 112 +- AI/StupidAI/StupidAI.cpp | 24 +- AI/StupidAI/StupidAI.h | 112 +- AI/VCAI/Goals/Explore.cpp | 896 +++---- AI/VCAI/Goals/Explore.h | 132 +- client/CPlayerInterface.cpp | 2 +- client/CPlayerInterface.h | 2 +- client/battle/BattleActionsController.cpp | 14 +- client/battle/BattleAnimationClasses.cpp | 8 +- client/battle/BattleAnimationClasses.h | 10 +- client/battle/BattleFieldController.cpp | 82 +- client/battle/BattleFieldController.h | 282 +-- client/battle/BattleInterface.cpp | 2 +- client/battle/BattleInterface.h | 464 ++-- client/battle/BattleStacksController.cpp | 4 +- client/battle/BattleStacksController.h | 297 +-- client/mapView/MapRenderer.cpp | 20 +- client/renderSDL/SDL_Extensions.cpp | 2 +- client/xBRZ/xbrz.cpp | 4 +- client/xBRZ/xbrz.h | 2 +- client/xBRZ/xbrz_tools.h | 8 +- include/vstd/RNG.h | 207 +- lib/BattleFieldHandler.cpp | 202 +- lib/BattleFieldHandler.h | 160 +- lib/CGameInterface.cpp | 2 +- lib/CGameInterface.h | 2 +- lib/CMakeLists.txt | 2 + lib/CStack.cpp | 12 +- lib/CStack.h | 4 +- lib/IGameEventsReceiver.h | 4 +- lib/ObstacleHandler.cpp | 259 ++- lib/ObstacleHandler.h | 158 +- lib/battle/BattleHex.cpp | 383 ++- lib/battle/BattleHex.h | 244 +- lib/battle/BattleHexArray.cpp | 103 + lib/battle/BattleHexArray.h | 300 +++ lib/battle/BattleInfo.cpp | 10 +- lib/battle/CBattleInfoCallback.cpp | 118 +- lib/battle/CBattleInfoCallback.h | 28 +- lib/battle/CObstacleInstance.cpp | 22 +- lib/battle/CObstacleInstance.h | 12 +- lib/battle/Destination.h | 86 +- lib/battle/IBattleInfoCallback.h | 178 +- lib/battle/ReachabilityInfo.cpp | 180 +- lib/battle/ReachabilityInfo.h | 8 +- lib/battle/Unit.cpp | 482 ++-- lib/battle/Unit.h | 19 +- lib/bonuses/BonusEnum.h | 2 +- lib/bonuses/Limiters.cpp | 2 +- lib/bonuses/Limiters.h | 4 +- lib/mapping/CMap.cpp | 2 +- lib/networkPacks/PacksForClientBattle.h | 5 +- lib/pathfinder/CGPathNode.h | 486 ++-- lib/rmg/CZonePlacer.cpp | 2054 ++++++++--------- lib/rmg/modificators/ObstaclePlacer.cpp | 2 +- lib/serializer/BinaryDeserializer.h | 14 + lib/serializer/BinarySerializer.h | 1 + lib/spells/BattleSpellMechanics.cpp | 21 +- lib/spells/BattleSpellMechanics.h | 172 +- lib/spells/CSpellHandler.cpp | 4 +- lib/spells/CSpellHandler.h | 2 +- lib/spells/ISpellMechanics.h | 736 +++--- lib/spells/effects/Effect.h | 165 +- lib/spells/effects/LocationEffect.cpp | 104 +- lib/spells/effects/LocationEffect.h | 82 +- lib/spells/effects/Moat.cpp | 14 +- lib/spells/effects/Moat.h | 86 +- lib/spells/effects/Obstacle.cpp | 11 +- lib/spells/effects/Obstacle.h | 156 +- lib/spells/effects/Summon.cpp | 4 +- lib/spells/effects/Summon.h | 110 +- lib/spells/effects/Teleport.cpp | 4 +- lib/spells/effects/UnitEffect.cpp | 8 +- lib/spells/effects/UnitEffect.h | 122 +- server/battles/BattleActionProcessor.cpp | 14 +- server/battles/BattleFlowProcessor.cpp | 48 +- server/battles/BattleFlowProcessor.h | 123 +- 85 files changed, 5357 insertions(+), 5036 deletions(-) create mode 100644 lib/battle/BattleHexArray.cpp create mode 100644 lib/battle/BattleHexArray.h diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index c55de09b2..874ea8321 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -326,9 +326,9 @@ AttackPossibility AttackPossibility::evaluate( AttackPossibility bestAp(hex, BattleHex::INVALID, attackInfo); - std::vector defenderHex; + BattleHexArray defenderHex; if(attackInfo.shooting) - defenderHex.push_back(defender->getPosition()); + defenderHex.insert(defender->getPosition()); else defenderHex = CStack::meleeAttackHexes(attacker, defender, hex); diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 33f31eb9c..6beb15dc4 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -355,7 +355,7 @@ BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex, } } -BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector hexes, const PotentialTargets & targets) +BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexArray hexes, const PotentialTargets & targets) { auto reachability = cb->getBattle(battleID)->getReachability(stack); auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false); @@ -381,7 +381,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector return BattleAction::makeDefend(stack); } - std::vector targetHexes = hexes; + BattleHexArray targetHexes = hexes; vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); }); @@ -419,16 +419,16 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector if(stack->hasBonusOfType(BonusType::FLYING)) { - std::set obstacleHexes; + BattleHexArray obstacleHexes; - auto insertAffected = [](const CObstacleInstance & spellObst, std::set & obstacleHexes) { + auto insertAffected = [](const CObstacleInstance & spellObst, BattleHexArray & obstacleHexes) { auto affectedHexes = spellObst.getAffectedTiles(); - obstacleHexes.insert(affectedHexes.cbegin(), affectedHexes.cend()); + obstacleHexes.merge(affectedHexes); }; const auto & obstacles = hb->battleGetAllObstacles(); - for (const auto & obst: obstacles) { + for (const auto & obst : obstacles) { if(obst->triggersEffects()) { diff --git a/AI/BattleAI/BattleEvaluator.h b/AI/BattleAI/BattleEvaluator.h index 4c4bb1e5b..62a6c22eb 100644 --- a/AI/BattleAI/BattleEvaluator.h +++ b/AI/BattleAI/BattleEvaluator.h @@ -51,7 +51,7 @@ public: bool attemptCastingSpell(const CStack * stack); bool canCastSpell(); std::optional findBestCreatureSpell(const CStack * stack); - BattleAction goTowardsNearest(const CStack * stack, std::vector hexes, const PotentialTargets & targets); + BattleAction goTowardsNearest(const CStack * stack, BattleHexArray hexes, const PotentialTargets & targets); std::vector getBrokenWallMoatHexes() const; bool hasWorkingTowers() const; void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 60b6b4b02..2aba3c771 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -458,7 +458,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( } } - result.positions.push_back(enemyHex); + result.positions.insert(enemyHex); } result.cachedAttack = attack; @@ -487,12 +487,12 @@ std::vector BattleExchangeEvaluator::getAdjacentUnits(cons auto hexes = stack->getSurroundingHexes(); for(auto hex : hexes) { - auto neighbor = cb->battleGetUnitByPos(hex); + auto neighbour = cb->battleGetUnitByPos(hex); - if(neighbor && neighbor->unitSide() == stack->unitSide() && !vstd::contains(checkedStacks, neighbor)) + if(neighbour && neighbour->unitSide() == stack->unitSide() && !vstd::contains(checkedStacks, neighbour)) { - queue.push(neighbor); - checkedStacks.push_back(neighbor); + queue.push(neighbour); + checkedStacks.push_back(neighbour); } } } @@ -511,7 +511,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( auto hexes = ap.attack.defender->getSurroundingHexes(); - if(!ap.attack.shooting) hexes.push_back(ap.from); + if(!ap.attack.shooting) hexes.insert(ap.from); std::vector allReachableUnits = additionalUnits; @@ -959,9 +959,11 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) { - for(BattleHex neighbor : hex.neighbouringTiles()) + BattleHexArray neighbours; + neighbours.generateNeighbouringTiles(hex); + for(BattleHex neighbour : neighbours) { - reachable = unitReachability.distances.at(neighbor) <= radius; + reachable = unitReachability.distances.at(neighbour) <= radius; if(reachable) break; } @@ -1020,10 +1022,9 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) { enemyUnit = true; - - for(BattleHex neighbor : hex.neighbouringTiles()) + for(BattleHex neighbour : BattleHexArray::generateNeighbouringTiles(hex)) { - reachable = unitReachability.distances.at(neighbor) <= unitSpeed; + reachable = unitReachability.distances.at(neighbour) <= unitSpeed; if(reachable) break; } diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index dbbbaab3d..844890778 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -54,7 +54,7 @@ struct AttackerValue struct MoveTarget { float score; - std::vector positions; + BattleHexArray positions; std::optional cachedAttack; uint8_t turnsToRich; diff --git a/AI/Nullkiller/Helpers/ExplorationHelper.h b/AI/Nullkiller/Helpers/ExplorationHelper.h index cd4427de1..0d58f341d 100644 --- a/AI/Nullkiller/Helpers/ExplorationHelper.h +++ b/AI/Nullkiller/Helpers/ExplorationHelper.h @@ -1,49 +1,49 @@ -/* -* ExplorationHelper.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#pragma once - -#include "../AIUtility.h" - -#include "../../../lib/GameConstants.h" -#include "../../../lib/VCMI_Lib.h" -#include "../Goals/AbstractGoal.h" - -namespace NKAI -{ - -class ExplorationHelper -{ -private: - const CGHeroInstance * hero; - int sightRadius; - float bestValue; - Goals::TSubgoal bestGoal; - int3 bestTile; - int bestTilesDiscovered; - const Nullkiller * ai; - CCallback * cbp; - const TeamState * ts; - int3 ourPos; - bool allowDeadEndCancellation; - bool useCPathfinderAccessibility; - -public: - ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false); - Goals::TSubgoal makeComposition() const; - bool scanSector(int scanRadius); - bool scanMap(); - int howManyTilesWillBeDiscovered(const int3 & pos) const; - -private: - void scanTile(const int3 & tile); - bool hasReachableNeighbor(const int3 & pos) const; -}; - -} +/* +* ExplorationHelper.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "../AIUtility.h" + +#include "../../../lib/GameConstants.h" +#include "../../../lib/VCMI_Lib.h" +#include "../Goals/AbstractGoal.h" + +namespace NKAI +{ + +class ExplorationHelper +{ +private: + const CGHeroInstance * hero; + int sightRadius; + float bestValue; + Goals::TSubgoal bestGoal; + int3 bestTile; + int bestTilesDiscovered; + const Nullkiller * ai; + CCallback * cbp; + const TeamState * ts; + int3 ourPos; + bool allowDeadEndCancellation; + bool useCPathfinderAccessibility; + +public: + ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false); + Goals::TSubgoal makeComposition() const; + bool scanSector(int scanRadius); + bool scanMap(); + int howManyTilesWillBeDiscovered(const int3 & pos) const; + +private: + void scanTile(const int3 & tile); + bool hasReachableneighbour(const int3 & pos) const; +}; + +} diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index a40fbd7d2..de9ad89b3 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -399,7 +399,7 @@ void AINodeStorage::calculateNeighbours( #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( - "Node %s added to neighbors of %s, layer %d", + "Node %s added to neighbours of %s, layer %d", neighbour.toString(), source.coord.toString(), static_cast(layer)); diff --git a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp index 8905572c9..ddaff0e73 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp @@ -56,9 +56,9 @@ void ObjectGraphCalculator::calculateConnections() removeExtraConnections(); } -float ObjectGraphCalculator::getNeighborConnectionsCost(const int3 & pos, std::vector & pathCache) +float ObjectGraphCalculator::getneighbourConnectionsCost(const int3 & pos, std::vector & pathCache) { - float neighborCost = std::numeric_limits::max(); + float neighbourCost = std::numeric_limits::max(); if(NKAI_GRAPH_TRACE_LEVEL >= 2) { @@ -68,24 +68,24 @@ float ObjectGraphCalculator::getNeighborConnectionsCost(const int3 & pos, std::v foreach_neighbour( ai->cb.get(), pos, - [this, &neighborCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) + [this, &neighbourCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour) { - ai->pathfinder->calculatePathInfo(pathCache, neighbor); + ai->pathfinder->calculatePathInfo(pathCache, neighbour); auto costTotal = this->getConnectionsCost(pathCache); - if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost) + if(costTotal.connectionsCount > 2 && costTotal.avg < neighbourCost) { - neighborCost = costTotal.avg; + neighbourCost = costTotal.avg; if(NKAI_GRAPH_TRACE_LEVEL >= 2) { - logAi->trace("Better node found at %s", neighbor.toString()); + logAi->trace("Better node found at %s", neighbour.toString()); } } }); - return neighborCost; + return neighbourCost; } void ObjectGraphCalculator::addMinimalDistanceJunctions() @@ -105,9 +105,9 @@ void ObjectGraphCalculator::addMinimalDistanceJunctions() if(currentCost.connectionsCount <= 2) return; - float neighborCost = getNeighborConnectionsCost(pos, paths); + float neighbourCost = getneighbourConnectionsCost(pos, paths); - if(currentCost.avg < neighborCost) + if(currentCost.avg < neighbourCost) { junctions.insert(pos); } @@ -137,17 +137,17 @@ void ObjectGraphCalculator::calculateConnections(const int3 & pos, std::vectorcb.get(), pos, - [this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) + [this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour) { - if(target->hasNodeAt(neighbor)) + if(target->hasNodeAt(neighbour)) { - ai->pathfinder->calculatePathInfo(pathCache, neighbor); + ai->pathfinder->calculatePathInfo(pathCache, neighbour); for(auto & path : pathCache) { if(pos == path.targetHero->visitablePos()) { - target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger()); + target->tryAddConnection(pos, neighbour, path.movementCost(), path.getTotalDanger()); } } } diff --git a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h index 812bd6985..67b03edff 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h @@ -1,56 +1,56 @@ -/* -* ObjectGraphCalculator.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ - -#pragma once - -#include "ObjectGraph.h" -#include "../AIUtility.h" - -namespace NKAI -{ - -struct ConnectionCostInfo -{ - float totalCost = 0; - float avg = 0; - int connectionsCount = 0; -}; - -class ObjectGraphCalculator -{ -private: - ObjectGraph * target; - const Nullkiller * ai; - std::mutex syncLock; - - std::map actors; - std::map actorObjectMap; - - std::vector> temporaryBoats; - std::vector> temporaryActorHeroes; - -public: - ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai); - void setGraphObjects(); - void calculateConnections(); - float getNeighborConnectionsCost(const int3 & pos, std::vector & pathCache); - void addMinimalDistanceJunctions(); - -private: - void updatePaths(); - void calculateConnections(const int3 & pos, std::vector & pathCache); - bool isExtraConnection(float direct, float side1, float side2) const; - void removeExtraConnections(); - void addObjectActor(const CGObjectInstance * obj); - void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false); - ConnectionCostInfo getConnectionsCost(std::vector & paths) const; -}; - -} +/* +* ObjectGraphCalculator.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "ObjectGraph.h" +#include "../AIUtility.h" + +namespace NKAI +{ + +struct ConnectionCostInfo +{ + float totalCost = 0; + float avg = 0; + int connectionsCount = 0; +}; + +class ObjectGraphCalculator +{ +private: + ObjectGraph * target; + const Nullkiller * ai; + std::mutex syncLock; + + std::map actors; + std::map actorObjectMap; + + std::vector> temporaryBoats; + std::vector> temporaryActorHeroes; + +public: + ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai); + void setGraphObjects(); + void calculateConnections(); + float getneighbourConnectionsCost(const int3 & pos, std::vector & pathCache); + void addMinimalDistanceJunctions(); + +private: + void updatePaths(); + void calculateConnections(const int3 & pos, std::vector & pathCache); + bool isExtraConnection(float direct, float side1, float side2) const; + void removeExtraConnections(); + void addObjectActor(const CGObjectInstance * obj); + void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false); + ConnectionCostInfo getConnectionsCost(std::vector & paths) const; +}; + +} diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 53919bb70..79c28f41c 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -69,7 +69,7 @@ public: const CStack * s; int adi; int adr; - std::vector attackFrom; //for melee fight + BattleHexArray attackFrom; //for melee fight EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0) {} void calcDmg(std::shared_ptr cb, const BattleID & battleID, const CStack * ourStack) @@ -107,7 +107,7 @@ static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr for(int i = 0; i < 2; i++) { - for (auto & neighbour : (i ? h2 : h1).neighbouringTiles()) + for (auto neighbour : BattleHexArray::generateNeighbouringTiles(i ? h2 : h1)) if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour)) if(s->isShooter()) shooters[i]++; @@ -157,7 +157,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack) } else { - std::vector avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(stack, false); + BattleHexArray avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(stack, false); for (BattleHex hex : avHexes) { @@ -170,7 +170,7 @@ void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack) i = enemiesReachable.begin() + (enemiesReachable.size() - 1); } - i->attackFrom.push_back(hex); + i->attackFrom.insert(hex); } } @@ -247,7 +247,7 @@ void CStupidAI::battleNewRound(const BattleID & battleID) print("battleNewRound called"); } -void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) +void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) { print("battleStackMoved called"); } @@ -278,7 +278,7 @@ void CStupidAI::print(const std::string &text) const logAi->trace("CStupidAI [%p]: %s", this, text); } -BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stack, std::vector hexes) const +BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const { auto reachability = cb->getBattle(battleID)->getReachability(stack); auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false); @@ -295,7 +295,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac for(auto hex : hexes) { - if(vstd::contains(avHexes, hex)) + if(avHexes.contains(hex)) { if(stack->position == hex) return BattleAction::makeDefend(stack); @@ -310,9 +310,9 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac } } - BattleHex bestNeighbor = hexes.front(); + BattleHex bestneighbour = hexes.front(); - if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE) + if(reachability.distances[bestneighbour] > GameConstants::BFIELD_SIZE) { return BattleAction::makeDefend(stack); } @@ -323,14 +323,14 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac // We just check all available hexes and pick the one closest to the target. auto nearestAvailableHex = vstd::minElementByFun(avHexes, [&](BattleHex hex) -> int { - return BattleHex::getDistance(bestNeighbor, hex); + return BattleHex::getDistance(bestneighbour, hex); }); return BattleAction::makeMove(stack, *nearestAvailableHex); } else { - BattleHex currentDest = bestNeighbor; + BattleHex currentDest = bestneighbour; while(1) { if(!currentDest.isValid()) @@ -339,7 +339,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac return BattleAction::makeDefend(stack); } - if(vstd::contains(avHexes, currentDest)) + if(avHexes.contains(currentDest)) { if(stack->position == currentDest) return BattleAction::makeDefend(stack); diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index 302bd5590..f1fe9171b 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -1,56 +1,56 @@ -/* - * StupidAI.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../../lib/battle/BattleHex.h" -#include "../../lib/battle/ReachabilityInfo.h" -#include "../../lib/CGameInterface.h" - -class EnemyInfo; - -class CStupidAI : public CBattleGameInterface -{ - BattleSide side; - std::shared_ptr cb; - std::shared_ptr env; - - bool wasWaitingForRealize; - bool wasUnlockingGs; - - void print(const std::string &text) const; -public: - CStupidAI(); - ~CStupidAI(); - - void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; - void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; - - void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero - void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero - void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack - void yourTacticPhase(const BattleID & battleID, int distance) override; - - void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack - void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) override; //called when stack receives damage (after battleAttack()) - void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; - //void battleResultsApplied() override; //called when all effects of last battle are applied - void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; - void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn - void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) override; - void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; - void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks - //void battleTriggerEffect(const BattleTriggerEffect & bte) override; - void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right - void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack - -private: - BattleAction goTowards(const BattleID & battleID, const CStack * stack, std::vector hexes) const; -}; - +/* + * StupidAI.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../../lib/battle/BattleHex.h" +#include "../../lib/battle/ReachabilityInfo.h" +#include "../../lib/CGameInterface.h" + +class EnemyInfo; + +class CStupidAI : public CBattleGameInterface +{ + BattleSide side; + std::shared_ptr cb; + std::shared_ptr env; + + bool wasWaitingForRealize; + bool wasUnlockingGs; + + void print(const std::string &text) const; +public: + CStupidAI(); + ~CStupidAI(); + + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; + + void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero + void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero + void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack + void yourTacticPhase(const BattleID & battleID, int distance) override; + + void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack + void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) override; //called when stack receives damage (after battleAttack()) + void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; + //void battleResultsApplied() override; //called when all effects of last battle are applied + void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; + void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn + void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; + void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; + void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks + //void battleTriggerEffect(const BattleTriggerEffect & bte) override; + void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right + void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack + +private: + BattleAction goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const; +}; + diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp index 294103598..55527c056 100644 --- a/AI/VCAI/Goals/Explore.cpp +++ b/AI/VCAI/Goals/Explore.cpp @@ -1,448 +1,448 @@ -/* -* Explore.cpp, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#include "StdInc.h" -#include "Goals.h" -#include "../VCAI.h" -#include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" -#include "../ResourceManager.h" -#include "../BuildingManager.h" -#include "../../../lib/constants/StringConstants.h" -#include "../../../lib/CPlayerState.h" - -using namespace Goals; - -namespace Goals -{ - struct ExplorationHelper - { - HeroPtr hero; - int sightRadius; - float bestValue; - TSubgoal bestGoal; - VCAI * aip; - CCallback * cbp; - const TeamState * ts; - int3 ourPos; - bool allowDeadEndCancellation; - bool allowGatherArmy; - - ExplorationHelper(HeroPtr h, bool gatherArmy) - { - cbp = cb; - aip = ai; - hero = h; - ts = cbp->getPlayerTeam(ai->playerID); - sightRadius = hero->getSightRadius(); - bestGoal = sptr(Goals::Invalid()); - bestValue = 0; - ourPos = h->visitablePos(); - allowDeadEndCancellation = true; - allowGatherArmy = gatherArmy; - } - - void scanSector(int scanRadius) - { - int3 tile = int3(0, 0, ourPos.z); - - const auto & slice = ts->fogOfWarMap[ourPos.z]; - - for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++) - { - for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++) - { - - if(cbp->isInTheMap(tile) && slice[tile.x][tile.y]) - { - scanTile(tile); - } - } - } - } - - void scanMap() - { - int3 mapSize = cbp->getMapSize(); - int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); - - std::vector from; - std::vector to; - - from.reserve(perimeter); - to.reserve(perimeter); - - foreach_tile_pos([&](const int3 & pos) - { - if(ts->fogOfWarMap[pos.z][pos.x][pos.y]) - { - bool hasInvisibleNeighbor = false; - - foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) - { - if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) - { - hasInvisibleNeighbor = true; - } - }); - - if(hasInvisibleNeighbor) - from.push_back(pos); - } - }); - - logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name); - - for(const int3 & tile : from) - { - scanTile(tile); - } - - if(!bestGoal->invalid()) - { - return; - } - - allowDeadEndCancellation = false; - - for(int i = 0; i < sightRadius; i++) - { - getVisibleNeighbours(from, to); - vstd::concatenate(from, to); - vstd::removeDuplicates(from); - } - - logAi->debug("Exploration scan all possible tiles for hero %s", hero.name); - - for(const int3 & tile : from) - { - scanTile(tile); - } - } - - void scanTile(const int3 & tile) - { - if(tile == ourPos - || !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does - return; - - int tilesDiscovered = howManyTilesWillBeDiscovered(tile); - if(!tilesDiscovered) - return; - - auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy); - for(auto goal : waysToVisit) - { - if(goal->evaluationContext.movementCost <= 0.0) // should not happen - continue; - - float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; - - if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much - { - auto obj = cb->getTopObj(tile); - - // picking up resources does not yield any exploration at all. - // if it blocks the way to some explorable tile AIPathfinder will take care of it - if(obj && obj->isBlockedVisitable()) - { - continue; - } - - if(isSafeToVisit(hero, tile)) - { - bestGoal = goal; - bestValue = ourValue; - } - } - } - } - - void getVisibleNeighbours(const std::vector & tiles, std::vector & out) const - { - for(const int3 & tile : tiles) - { - foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) - { - if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) - { - out.push_back(neighbour); - } - }); - } - } - - int howManyTilesWillBeDiscovered(const int3 & pos) const - { - int ret = 0; - int3 npos = int3(0, 0, pos.z); - - const auto & slice = ts->fogOfWarMap[pos.z]; - - for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++) - { - for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++) - { - if(cbp->isInTheMap(npos) - && pos.dist2d(npos) - 0.5 < sightRadius - && !slice[npos.x][npos.y]) - { - if(allowDeadEndCancellation - && !hasReachableNeighbor(npos)) - { - continue; - } - - ret++; - } - } - } - - return ret; - } - - bool hasReachableNeighbor(const int3 &pos) const - { - for(crint3 dir : int3::getDirs()) - { - int3 tile = pos + dir; - if(cbp->isInTheMap(tile)) - { - auto isAccessible = aip->ah->isTileAccessible(hero, tile); - - if(isAccessible) - return true; - } - } - - return false; - } - }; -} - -bool Explore::operator==(const Explore & other) const -{ - return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; -} - -std::string Explore::completeMessage() const -{ - return "Hero " + hero.get()->getNameTranslated() + " completed exploration"; -} - -TSubgoal Explore::whatToDoToAchieve() -{ - return fh->chooseSolution(getAllPossibleSubgoals()); -} - -TGoalVec Explore::getAllPossibleSubgoals() -{ - TGoalVec ret; - std::vector heroes; - - if(hero) - { - heroes.push_back(hero.h); - } - else - { - //heroes = ai->getUnblockedHeroes(); - heroes = cb->getHeroesInfo(); - vstd::erase_if(heroes, [](const HeroPtr h) - { - if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer - return true; - - if(!ai->isAbleToExplore(h)) - return true; - - return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway - }); - } - - //try to use buildings that uncover map - std::vector objs; - for(auto obj : ai->visitableObjs) - { - if(!vstd::contains(ai->alreadyVisited, obj)) - { - switch(obj->ID.num) - { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - case Obj::CARTOGRAPHER: - objs.push_back(obj); - break; - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); - if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) - objs.push_back(obj); - break; - } - } - else - { - switch(obj->ID.num) - { - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) - break; - for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) - { - if(!cb->getObj(exit)) - { // Always attempt to visit two-way teleports if one of channel exits is not visible - objs.push_back(obj); - break; - } - } - break; - } - } - } - - for(auto h : heroes) - { - for(auto obj : objs) //double loop, performance risk? - { - auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy); - - vstd::concatenate(ret, waysToVisitObj); - } - - TSubgoal goal = exploreNearestNeighbour(h); - - if(!goal->invalid()) - { - ret.push_back(goal); - } - } - - if(ret.empty()) - { - for(auto h : heroes) - { - logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated()); - - TSubgoal goal = explorationNewPoint(h); - - if(goal->invalid()) - { - ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore - } - else - { - ret.push_back(goal); - } - } - } - - //we either don't have hero yet or none of heroes can explore - if((!hero || ret.empty()) && ai->canRecruitAnyHero()) - ret.push_back(sptr(RecruitHero())); - - if(ret.empty()) - { - throw goalFulfilledException(sptr(Explore().sethero(hero))); - } - - return ret; -} - -bool Explore::fulfillsMe(TSubgoal goal) -{ - if(goal->goalType == EXPLORE) - { - if(goal->hero) - return hero == goal->hero; - else - return true; //cancel ALL exploration - } - return false; -} - -TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const -{ - ExplorationHelper scanResult(h, allowGatherArmy); - - for(crint3 dir : int3::getDirs()) - { - int3 tile = hpos + dir; - if(cb->isInTheMap(tile)) - { - scanResult.scanTile(tile); - } - } - - return scanResult.bestGoal; -} - - -TSubgoal Explore::explorationNewPoint(HeroPtr h) const -{ - ExplorationHelper scanResult(h, allowGatherArmy); - - scanResult.scanSector(10); - - if(!scanResult.bestGoal->invalid()) - { - return scanResult.bestGoal; - } - - scanResult.scanMap(); - - return scanResult.bestGoal; -} - - -TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const -{ - TimeCheck tc("where to explore"); - int3 hpos = h->visitablePos(); - - //look for nearby objs -> visit them if they're close enough - const int DIST_LIMIT = 3; - const float COST_LIMIT = .2f; //todo: fine tune - - std::vector nearbyVisitableObjs; - for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map - { - for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y) - { - for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false)) - { - if(ai->isGoodForVisit(obj, h, COST_LIMIT)) - { - nearbyVisitableObjs.push_back(obj); - } - } - } - } - - if(nearbyVisitableObjs.size()) - { - vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles - boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); - - TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false)); - - if(!pickupNearestObj->invalid()) - { - return pickupNearestObj; - } - } - - //check if nearby tiles allow us to reveal anything - this is quick - return explorationBestNeighbour(hpos, h); -} +/* +* Explore.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/constants/StringConstants.h" +#include "../../../lib/CPlayerState.h" + +using namespace Goals; + +namespace Goals +{ + struct ExplorationHelper + { + HeroPtr hero; + int sightRadius; + float bestValue; + TSubgoal bestGoal; + VCAI * aip; + CCallback * cbp; + const TeamState * ts; + int3 ourPos; + bool allowDeadEndCancellation; + bool allowGatherArmy; + + ExplorationHelper(HeroPtr h, bool gatherArmy) + { + cbp = cb; + aip = ai; + hero = h; + ts = cbp->getPlayerTeam(ai->playerID); + sightRadius = hero->getSightRadius(); + bestGoal = sptr(Goals::Invalid()); + bestValue = 0; + ourPos = h->visitablePos(); + allowDeadEndCancellation = true; + allowGatherArmy = gatherArmy; + } + + void scanSector(int scanRadius) + { + int3 tile = int3(0, 0, ourPos.z); + + const auto & slice = ts->fogOfWarMap[ourPos.z]; + + for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++) + { + for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++) + { + + if(cbp->isInTheMap(tile) && slice[tile.x][tile.y]) + { + scanTile(tile); + } + } + } + } + + void scanMap() + { + int3 mapSize = cbp->getMapSize(); + int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); + + std::vector from; + std::vector to; + + from.reserve(perimeter); + to.reserve(perimeter); + + foreach_tile_pos([&](const int3 & pos) + { + if(ts->fogOfWarMap[pos.z][pos.x][pos.y]) + { + bool hasInvisibleneighbour = false; + + foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) + { + if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) + { + hasInvisibleneighbour = true; + } + }); + + if(hasInvisibleneighbour) + from.push_back(pos); + } + }); + + logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + + if(!bestGoal->invalid()) + { + return; + } + + allowDeadEndCancellation = false; + + for(int i = 0; i < sightRadius; i++) + { + getVisibleNeighbours(from, to); + vstd::concatenate(from, to); + vstd::removeDuplicates(from); + } + + logAi->debug("Exploration scan all possible tiles for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + } + + void scanTile(const int3 & tile) + { + if(tile == ourPos + || !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does + return; + + int tilesDiscovered = howManyTilesWillBeDiscovered(tile); + if(!tilesDiscovered) + return; + + auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy); + for(auto goal : waysToVisit) + { + if(goal->evaluationContext.movementCost <= 0.0) // should not happen + continue; + + float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; + + if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much + { + auto obj = cb->getTopObj(tile); + + // picking up resources does not yield any exploration at all. + // if it blocks the way to some explorable tile AIPathfinder will take care of it + if(obj && obj->isBlockedVisitable()) + { + continue; + } + + if(isSafeToVisit(hero, tile)) + { + bestGoal = goal; + bestValue = ourValue; + } + } + } + } + + void getVisibleNeighbours(const std::vector & tiles, std::vector & out) const + { + for(const int3 & tile : tiles) + { + foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) + { + if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) + { + out.push_back(neighbour); + } + }); + } + } + + int howManyTilesWillBeDiscovered(const int3 & pos) const + { + int ret = 0; + int3 npos = int3(0, 0, pos.z); + + const auto & slice = ts->fogOfWarMap[pos.z]; + + for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++) + { + for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++) + { + if(cbp->isInTheMap(npos) + && pos.dist2d(npos) - 0.5 < sightRadius + && !slice[npos.x][npos.y]) + { + if(allowDeadEndCancellation + && !hasReachableneighbour(npos)) + { + continue; + } + + ret++; + } + } + } + + return ret; + } + + bool hasReachableneighbour(const int3 &pos) const + { + for(crint3 dir : int3::getDirs()) + { + int3 tile = pos + dir; + if(cbp->isInTheMap(tile)) + { + auto isAccessible = aip->ah->isTileAccessible(hero, tile); + + if(isAccessible) + return true; + } + } + + return false; + } + }; +} + +bool Explore::operator==(const Explore & other) const +{ + return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; +} + +std::string Explore::completeMessage() const +{ + return "Hero " + hero.get()->getNameTranslated() + " completed exploration"; +} + +TSubgoal Explore::whatToDoToAchieve() +{ + return fh->chooseSolution(getAllPossibleSubgoals()); +} + +TGoalVec Explore::getAllPossibleSubgoals() +{ + TGoalVec ret; + std::vector heroes; + + if(hero) + { + heroes.push_back(hero.h); + } + else + { + //heroes = ai->getUnblockedHeroes(); + heroes = cb->getHeroesInfo(); + vstd::erase_if(heroes, [](const HeroPtr h) + { + if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer + return true; + + if(!ai->isAbleToExplore(h)) + return true; + + return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway + }); + } + + //try to use buildings that uncover map + std::vector objs; + for(auto obj : ai->visitableObjs) + { + if(!vstd::contains(ai->alreadyVisited, obj)) + { + switch(obj->ID.num) + { + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: + case Obj::CARTOGRAPHER: + objs.push_back(obj); + break; + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); + if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) + objs.push_back(obj); + break; + } + } + else + { + switch(obj->ID.num) + { + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) + break; + for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) + { + if(!cb->getObj(exit)) + { // Always attempt to visit two-way teleports if one of channel exits is not visible + objs.push_back(obj); + break; + } + } + break; + } + } + } + + for(auto h : heroes) + { + for(auto obj : objs) //double loop, performance risk? + { + auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy); + + vstd::concatenate(ret, waysToVisitObj); + } + + TSubgoal goal = exploreNearestNeighbour(h); + + if(!goal->invalid()) + { + ret.push_back(goal); + } + } + + if(ret.empty()) + { + for(auto h : heroes) + { + logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated()); + + TSubgoal goal = explorationNewPoint(h); + + if(goal->invalid()) + { + ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore + } + else + { + ret.push_back(goal); + } + } + } + + //we either don't have hero yet or none of heroes can explore + if((!hero || ret.empty()) && ai->canRecruitAnyHero()) + ret.push_back(sptr(RecruitHero())); + + if(ret.empty()) + { + throw goalFulfilledException(sptr(Explore().sethero(hero))); + } + + return ret; +} + +bool Explore::fulfillsMe(TSubgoal goal) +{ + if(goal->goalType == EXPLORE) + { + if(goal->hero) + return hero == goal->hero; + else + return true; //cancel ALL exploration + } + return false; +} + +TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const +{ + ExplorationHelper scanResult(h, allowGatherArmy); + + for(crint3 dir : int3::getDirs()) + { + int3 tile = hpos + dir; + if(cb->isInTheMap(tile)) + { + scanResult.scanTile(tile); + } + } + + return scanResult.bestGoal; +} + + +TSubgoal Explore::explorationNewPoint(HeroPtr h) const +{ + ExplorationHelper scanResult(h, allowGatherArmy); + + scanResult.scanSector(10); + + if(!scanResult.bestGoal->invalid()) + { + return scanResult.bestGoal; + } + + scanResult.scanMap(); + + return scanResult.bestGoal; +} + + +TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const +{ + TimeCheck tc("where to explore"); + int3 hpos = h->visitablePos(); + + //look for nearby objs -> visit them if they're close enough + const int DIST_LIMIT = 3; + const float COST_LIMIT = .2f; //todo: fine tune + + std::vector nearbyVisitableObjs; + for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map + { + for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y) + { + for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false)) + { + if(ai->isGoodForVisit(obj, h, COST_LIMIT)) + { + nearbyVisitableObjs.push_back(obj); + } + } + } + } + + if(nearbyVisitableObjs.size()) + { + vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles + boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); + + TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false)); + + if(!pickupNearestObj->invalid()) + { + return pickupNearestObj; + } + } + + //check if nearby tiles allow us to reveal anything - this is quick + return explorationBestNeighbour(hpos, h); +} diff --git a/AI/VCAI/Goals/Explore.h b/AI/VCAI/Goals/Explore.h index a96de7f29..5c5b29679 100644 --- a/AI/VCAI/Goals/Explore.h +++ b/AI/VCAI/Goals/Explore.h @@ -1,66 +1,66 @@ -/* -* Explore.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#pragma once - -#include "CGoal.h" - -struct HeroPtr; -class VCAI; -class FuzzyHelper; - -namespace Goals -{ - struct ExplorationHelper; - - class DLL_EXPORT Explore : public CGoal - { - private: - bool allowGatherArmy; - - public: - Explore(bool allowGatherArmy) - : CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy) - { - priority = 1; - } - - Explore() - : Explore(true) - { - } - - Explore(HeroPtr h) - : CGoal(Goals::EXPLORE) - { - hero = h; - priority = 1; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - std::string completeMessage() const override; - bool fulfillsMe(TSubgoal goal) override; - bool operator==(const Explore & other) const override; - - private: - TSubgoal exploreNearestNeighbour(HeroPtr h) const; - TSubgoal explorationNewPoint(HeroPtr h) const; - TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const; - void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const; - bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; - - void getVisibleNeighbours( - const std::vector & tiles, - std::vector & out, - CCallback * cbp, - const TeamState * ts) const; - - int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const; - }; -} +/* +* Explore.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + struct ExplorationHelper; + + class DLL_EXPORT Explore : public CGoal + { + private: + bool allowGatherArmy; + + public: + Explore(bool allowGatherArmy) + : CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy) + { + priority = 1; + } + + Explore() + : Explore(true) + { + } + + Explore(HeroPtr h) + : CGoal(Goals::EXPLORE) + { + hero = h; + priority = 1; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + std::string completeMessage() const override; + bool fulfillsMe(TSubgoal goal) override; + bool operator==(const Explore & other) const override; + + private: + TSubgoal exploreNearestNeighbour(HeroPtr h) const; + TSubgoal explorationNewPoint(HeroPtr h) const; + TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const; + void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const; + bool hasReachableneighbour(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; + + void getVisibleNeighbours( + const std::vector & tiles, + std::vector & out, + CCallback * cbp, + const TeamState * ts) const; + + int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const; + }; +} diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 1462e7977..0b8e1e333 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -846,7 +846,7 @@ void CPlayerInterface::battleLogMessage(const BattleID & battleID, const std::ve battleInt->displayBattleLog(lines); } -void CPlayerInterface::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) +void CPlayerInterface::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) { EVENT_HANDLER_CALLED_BY_CLIENT; BATTLE_EVENT_POSSIBLE_RETURN; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index ab86dd194..ceed1d42e 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -154,7 +154,7 @@ protected: // Call-ins from server, should not be called directly, but only via void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; used for HP regen handling void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn void battleLogMessage(const BattleID & battleID, const std::vector & lines) override; - void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) override; + void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; //called when a specific effect is set to stacks void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte) override; //various one-shot effect diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 6c9784b80..9cbd79965 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -723,11 +723,11 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B { if(owner.stacksController->getActiveStack()->doubleWide()) { - std::vector acc = owner.getBattle()->battleGetAvailableHexes(owner.stacksController->getActiveStack(), false); + BattleHexArray acc = owner.getBattle()->battleGetAvailableHexes(owner.stacksController->getActiveStack(), false); BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false); - if(vstd::contains(acc, targetHex)) + if(acc.contains(targetHex)) owner.giveCommand(EActionType::WALK, targetHex); - else if(vstd::contains(acc, shiftedDest)) + else if(acc.contains(shiftedDest)) owner.giveCommand(EActionType::WALK, shiftedDest); } else @@ -1008,12 +1008,12 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell, bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const { - std::vector acc = owner.getBattle()->battleGetAvailableHexes(stackToMove, false); + BattleHexArray acc = owner.getBattle()->battleGetAvailableHexes(stackToMove, false); BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false); - if (vstd::contains(acc, myNumber)) + if (acc.contains(myNumber)) return true; - else if (stackToMove->doubleWide() && vstd::contains(acc, shiftedDest)) + else if (stackToMove->doubleWide() && acc.contains(shiftedDest)) return true; else return false; @@ -1126,4 +1126,4 @@ void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction void BattleActionsController::resetCurrentStackPossibleActions() { possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack()); -} +} \ No newline at end of file diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index 7d34e8978..0441e7d2e 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -422,7 +422,7 @@ MovementAnimation::~MovementAnimation() CCS->soundh->stopSound(moveSoundHandler); } -MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, std::vector _destTiles, int _distance) +MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, BattleHexArray _destTiles, int _distance) : StackMoveAnimation(owner, stack, stack->getPosition(), _destTiles.front()), destTiles(_destTiles), currentMoveIndex(0), @@ -892,7 +892,7 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & logAnim->debug("CPointEffectAnimation::init: effect %s", animationName.getName()); } -EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector hex, int effects, bool reversed): +EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHexArray hex, int effects, bool reversed): EffectAnimation(owner, animationName, effects, 1.0f, reversed) { battlehexes = hex; @@ -902,7 +902,7 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & EffectAnimation(owner, animationName, effects, transparencyFactor, reversed) { assert(hex.isValid()); - battlehexes.push_back(hex); + battlehexes.insert(hex); } EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector pos, int effects, bool reversed): @@ -921,7 +921,7 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & EffectAnimation(owner, animationName, effects, 1.0f, reversed) { assert(hex.isValid()); - battlehexes.push_back(hex); + battlehexes.insert(hex); positions.push_back(pos); } diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index 83233819b..a00ac82c3 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -9,7 +9,7 @@ */ #pragma once -#include "../../lib/battle/BattleHex.h" +#include "../../lib/battle/BattleHexArray.h" #include "../../lib/filesystem/ResourcePath.h" #include "BattleConstants.h" @@ -143,7 +143,7 @@ class MovementAnimation : public StackMoveAnimation private: int moveSoundHandler; // sound handler used when moving a unit - std::vector destTiles; //full path, includes already passed hexes + BattleHexArray destTiles; //full path, includes already passed hexes ui32 currentMoveIndex; // index of nextHex in destTiles double begX, begY; // starting position @@ -159,7 +159,7 @@ public: bool init() override; void tick(uint32_t msPassed) override; - MovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector _destTiles, int _distance); + MovementAnimation(BattleInterface & owner, const CStack *_stack, BattleHexArray _destTiles, int _distance); ~MovementAnimation(); }; @@ -316,7 +316,7 @@ class EffectAnimation : public BattleAnimation std::shared_ptr animation; std::vector positions; - std::vector battlehexes; + BattleHexArray battlehexes; bool alignToBottom() const; bool waitForSound() const; @@ -344,7 +344,7 @@ public: /// Create animation positioned at certain hex(es) EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex , int effects = 0, float transparencyFactor = 1.0f, bool reversed = false); - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector hex, int effects = 0, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHexArray hex, int effects = 0, bool reversed = false); EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects = 0, bool reversed = false); ~EffectAnimation(); diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 27c62535e..2206b24cb 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -267,7 +267,7 @@ void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas) void BattleFieldController::redrawBackgroundWithHexes() { const CStack *activeStack = owner.stacksController->getActiveStack(); - std::vector attackableHexes; + BattleHexArray attackableHexes; if(activeStack) occupiableHexes = owner.getBattle()->battleGetAvailableHexes(activeStack, false, true, &attackableHexes); @@ -280,8 +280,8 @@ void BattleFieldController::redrawBackgroundWithHexes() // show shaded hexes for active's stack valid movement and the hexes that it can attack if(settings["battle"]["stackRange"].Bool()) { - std::vector hexesToShade = occupiableHexes; - hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end()); + BattleHexArray hexesToShade = occupiableHexes; + hexesToShade.merge(attackableHexes); for(BattleHex hex : hexesToShade) { showHighlightedHex(*backgroundWithHexes, cellShade, hex, false); @@ -312,9 +312,9 @@ void BattleFieldController::showHighlightedHex(Canvas & canvas, std::shared_ptr< canvas.draw(cellBorder, hexPos); } -std::set BattleFieldController::getHighlightedHexesForActiveStack() +BattleHexArray BattleFieldController::getHighlightedHexesForActiveStack() { - std::set result; + BattleHexArray result; if(!owner.stacksController->getActiveStack()) return result; @@ -324,16 +324,16 @@ std::set BattleFieldController::getHighlightedHexesForActiveStack() auto hoveredHex = getHoveredHex(); - std::set set = owner.getBattle()->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex); + BattleHexArray set = owner.getBattle()->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex); for(BattleHex hex : set) result.insert(hex); return result; } -std::set BattleFieldController::getMovementRangeForHoveredStack() +BattleHexArray BattleFieldController::getMovementRangeForHoveredStack() { - std::set result; + BattleHexArray result; if (!owner.stacksController->getActiveStack()) return result; @@ -344,16 +344,16 @@ std::set BattleFieldController::getMovementRangeForHoveredStack() auto hoveredStack = getHoveredStack(); if(hoveredStack) { - std::vector v = owner.getBattle()->battleGetAvailableHexes(hoveredStack, true, true, nullptr); + BattleHexArray v = owner.getBattle()->battleGetAvailableHexes(hoveredStack, true, true, nullptr); for(BattleHex hex : v) result.insert(hex); } return result; } -std::set BattleFieldController::getHighlightedHexesForSpellRange() +BattleHexArray BattleFieldController::getHighlightedHexesForSpellRange() { - std::set result; + BattleHexArray result; auto hoveredHex = getHoveredHex(); const spells::Caster *caster = nullptr; @@ -378,7 +378,7 @@ std::set BattleFieldController::getHighlightedHexesForSpellRange() return result; } -std::set BattleFieldController::getHighlightedHexesForMovementTarget() +BattleHexArray BattleFieldController::getHighlightedHexesForMovementTarget() { const CStack * stack = owner.stacksController->getActiveStack(); auto hoveredHex = getHoveredHex(); @@ -386,7 +386,7 @@ std::set BattleFieldController::getHighlightedHexesForMovementTarget( if(!stack) return {}; - std::vector availableHexes = owner.getBattle()->battleGetAvailableHexes(stack, false, false, nullptr); + BattleHexArray availableHexes = owner.getBattle()->battleGetAvailableHexes(stack, false, false, nullptr); auto hoveredStack = owner.getBattle()->battleGetStackByPos(hoveredHex, true); if(owner.getBattle()->battleCanAttack(stack, hoveredStack, hoveredHex)) @@ -402,7 +402,7 @@ std::set BattleFieldController::getHighlightedHexesForMovementTarget( } } - if(vstd::contains(availableHexes, hoveredHex)) + if(availableHexes.contains(hoveredHex)) { if(stack->doubleWide()) return {hoveredHex, stack->occupiedHex(hoveredHex)}; @@ -412,7 +412,7 @@ std::set BattleFieldController::getHighlightedHexesForMovementTarget( if(stack->doubleWide()) { - for(auto const & hex : availableHexes) + for(auto hex : availableHexes) { if(stack->occupiedHex(hex) == hoveredHex) return {hoveredHex, hex}; @@ -424,9 +424,9 @@ std::set BattleFieldController::getHighlightedHexesForMovementTarget( // Range limit highlight helpers -std::vector BattleFieldController::getRangeHexes(BattleHex sourceHex, uint8_t distance) +BattleHexArray BattleFieldController::getRangeHexes(BattleHex sourceHex, uint8_t distance) { - std::vector rangeHexes; + BattleHexArray rangeHexes; if (!settings["battle"]["rangeLimitHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown()) return rangeHexes; @@ -436,27 +436,27 @@ std::vector BattleFieldController::getRangeHexes(BattleHex sourceHex, { BattleHex hex(i); if(hex.isAvailable() && BattleHex::getDistance(sourceHex, hex) <= distance) - rangeHexes.push_back(hex); + rangeHexes.insert(hex); } return rangeHexes; } -std::vector BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, std::vector rangeHexes, uint8_t distanceToLimit) +BattleHexArray BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray rangeHexes, uint8_t distanceToLimit) { - std::vector rangeLimitHexes; + BattleHexArray rangeLimitHexes; // from range hexes get only the ones at the limit for(auto & hex : rangeHexes) { if(BattleHex::getDistance(hoveredHex, hex) == distanceToLimit) - rangeLimitHexes.push_back(hex); + rangeLimitHexes.insert(hex); } return rangeLimitHexes; } -bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, std::vector & rangeLimitHexes, int * hexIndexInRangeLimit) +bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit) { bool hexInRangeLimit = false; @@ -470,18 +470,18 @@ bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, std::vector> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(std::vector wholeRangeHexes, std::vector rangeLimitHexes) +std::vector> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray wholeRangeHexes, BattleHexArray rangeLimitHexes) { std::vector> output; if(wholeRangeHexes.empty()) return output; - for(auto & hex : rangeLimitHexes) + for(auto hex : rangeLimitHexes) { // get all neighbours and their directions - auto neighbouringTiles = hex.allNeighbouringTiles(); + auto neighbouringTiles = BattleHexArray::generateAllNeighbouringTiles(hex); std::vector outsideNeighbourDirections; @@ -525,9 +525,9 @@ std::vector> BattleFieldController::calculateRangeLimitH return output; } -void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, std::vector & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights) +void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights) { - std::vector rangeHexes = getRangeHexes(hoveredHex, distance); + BattleHexArray rangeHexes = getRangeHexes(hoveredHex, distance); rangeLimitHexes = getRangeLimitHexes(hoveredHex, rangeHexes, distance); std::vector> rangeLimitNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangeHexes, rangeLimitHexes); rangeLimitHexesHighlights = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages); @@ -535,18 +535,18 @@ void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distan void BattleFieldController::showHighlightedHexes(Canvas & canvas) { - std::vector rangedFullDamageLimitHexes; - std::vector shootingRangeLimitHexes; + BattleHexArray rangedFullDamageLimitHexes; + BattleHexArray shootingRangeLimitHexes; std::vector> rangedFullDamageLimitHexesHighlights; std::vector> shootingRangeLimitHexesHighlights; - std::set hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack(); - std::set hoveredSpellHexes = getHighlightedHexesForSpellRange(); - std::set hoveredMoveHexes = getHighlightedHexesForMovementTarget(); + BattleHexArray hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack(); + BattleHexArray hoveredSpellHexes = getHighlightedHexesForSpellRange(); + BattleHexArray hoveredMoveHexes = getHighlightedHexesForMovementTarget(); BattleHex hoveredHex = getHoveredHex(); - std::set hoveredMouseHex = hoveredHex.isValid() ? std::set({ hoveredHex }) : std::set(); + BattleHexArray hoveredMouseHex = hoveredHex.isValid() ? BattleHexArray({ hoveredHex }) : BattleHexArray(); const CStack * hoveredStack = getHoveredStack(); if(!hoveredStack && hoveredHex == BattleHex::INVALID) @@ -573,8 +573,8 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas) for(int hex = 0; hex < GameConstants::BFIELD_SIZE; ++hex) { - bool stackMovement = hoveredStackMovementRangeHexes.count(hex); - bool mouse = hoveredMouseHexes.count(hex); + bool stackMovement = hoveredStackMovementRangeHexes.contains(hex); + bool mouse = hoveredMouseHexes.contains(hex); // calculate if hex is Ranged Full Damage Limit and its position in highlight array int hexIndexInRangedFullDamageLimit = 0; @@ -679,7 +679,7 @@ BattleHex BattleFieldController::getHexAtPosition(Point hoverPos) BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber) { const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide(); - auto neighbours = myNumber.allNeighbouringTiles(); + auto neighbours = BattleHexArray::generateAllNeighbouringTiles(myNumber); // 0 1 // 5 x 2 // 4 3 @@ -696,18 +696,18 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber) // | - - | - - | - - | - o o | o o - | - - | - - | o o for (size_t i : { 1, 2, 3}) - attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, neighbours[i].cloneInDirection(BattleHex::RIGHT, false)); + attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::RIGHT, false)); for (size_t i : { 4, 5, 0}) - attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, neighbours[i].cloneInDirection(BattleHex::LEFT, false)); + attackAvailability[i] = occupiableHexes.contains(neighbours[i]) && occupiableHexes.contains(neighbours[i].cloneInDirection(BattleHex::LEFT, false)); - attackAvailability[6] = vstd::contains(occupiableHexes, neighbours[0]) && vstd::contains(occupiableHexes, neighbours[1]); - attackAvailability[7] = vstd::contains(occupiableHexes, neighbours[3]) && vstd::contains(occupiableHexes, neighbours[4]); + attackAvailability[6] = occupiableHexes.contains(neighbours[0]) && occupiableHexes.contains(neighbours[1]); + attackAvailability[7] = occupiableHexes.contains(neighbours[3]) && occupiableHexes.contains(neighbours[4]); } else { for (size_t i = 0; i < 6; ++i) - attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]); + attackAvailability[i] = occupiableHexes.contains(neighbours[i]); attackAvailability[6] = false; attackAvailability[7] = false; diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index 4500e95ab..ecb6237af 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -1,141 +1,141 @@ -/* - * BattleFieldController.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../../lib/battle/BattleHex.h" -#include "../../lib/Point.h" -#include "../gui/CIntObject.h" - -VCMI_LIB_NAMESPACE_BEGIN -class CStack; -class Rect; -VCMI_LIB_NAMESPACE_END - -class BattleHero; -class CAnimation; -class Canvas; -class IImage; -class BattleInterface; - -/// Handles battlefield grid as well as rendering of background layer of battle interface -class BattleFieldController : public CIntObject -{ - BattleInterface & owner; - - std::shared_ptr background; - std::shared_ptr cellBorder; - std::shared_ptr cellUnitMovementHighlight; - std::shared_ptr cellUnitMaxMovementHighlight; - std::shared_ptr cellShade; - std::shared_ptr rangedFullDamageLimitImages; - std::shared_ptr shootingRangeLimitImages; - - std::shared_ptr attackCursors; - std::shared_ptr spellCursors; - - /// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack - std::unique_ptr backgroundWithHexes; - - /// direction which will be used to perform attack with current cursor position - Point currentAttackOriginPoint; - - /// hex currently under mouse hover - BattleHex hoveredHex; - - /// hexes to which currently active stack can move - std::vector occupiableHexes; - - /// hexes that when in front of a unit cause it's amount box to move back - std::array stackCountOutsideHexes; - - void showHighlightedHex(Canvas & to, std::shared_ptr highlight, BattleHex hex, bool darkBorder); - - std::set getHighlightedHexesForActiveStack(); - std::set getMovementRangeForHoveredStack(); - std::set getHighlightedHexesForSpellRange(); - std::set getHighlightedHexesForMovementTarget(); - - // Range limit highlight helpers - - /// get all hexes within a certain distance of given hex - std::vector getRangeHexes(BattleHex sourceHex, uint8_t distance); - - /// get only hexes at the limit of a range - std::vector getRangeLimitHexes(BattleHex hoveredHex, std::vector hexRange, uint8_t distanceToLimit); - - /// calculate if a hex is in range limit and return its index in range - bool IsHexInRangeLimit(BattleHex hex, std::vector & rangeLimitHexes, int * hexIndexInRangeLimit); - - /// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists - std::vector> getOutsideNeighbourDirectionsForLimitHexes(std::vector rangeHexes, std::vector rangeLimitHexes); - - /// calculates what image to use as range limit, depending on the direction of neighbors - /// a mask is used internally to mark the directions of all neighbours - /// based on this mask the corresponding image is selected - std::vector> calculateRangeLimitHighlightImages(std::vector> hexesNeighbourDirections, std::shared_ptr limitImages); - - /// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes - void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, std::vector & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights); - - void showBackground(Canvas & canvas); - void showBackgroundImage(Canvas & canvas); - void showBackgroundImageWithHexes(Canvas & canvas); - void showHighlightedHexes(Canvas & canvas); - void updateAccessibleHexes(); - - BattleHex getHexAtPosition(Point hoverPosition); - - /// Checks whether selected pixel is transparent, uses local coordinates of a hex - bool isPixelInHex(Point const & position); - size_t selectBattleCursor(BattleHex myNumber); - - void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; - void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override; - void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override; - void clickPressed(const Point & cursorPosition) override; - void showPopupWindow(const Point & cursorPosition) override; - void activate() override; - - void showAll(Canvas & to) override; - void show(Canvas & to) override; - void tick(uint32_t msPassed) override; - - bool receiveEvent(const Point & position, int eventType) const override; - -public: - BattleFieldController(BattleInterface & owner); - - void createHeroes(); - - void redrawBackgroundWithHexes(); - void renderBattlefield(Canvas & canvas); - - /// Returns position of hex relative to owner (BattleInterface) - Rect hexPositionLocal(BattleHex hex) const; - - /// Returns position of hex relative to game window - Rect hexPositionAbsolute(BattleHex hex) const; - - /// Returns ID of currently hovered hex or BattleHex::INVALID if none - BattleHex getHoveredHex(); - - /// Returns the currently hovered stack - const CStack* getHoveredStack(); - - /// returns true if selected tile can be attacked in melee by current stack - bool isTileAttackable(const BattleHex & number) const; - - /// returns true if stack should render its stack count image in default position - outside own hex - bool stackCountOutsideHex(const BattleHex & number) const; - - BattleHex::EDir selectAttackDirection(BattleHex myNumber); - - BattleHex fromWhichHexAttack(BattleHex myNumber); -}; +/* + * BattleFieldController.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../../lib/battle/BattleHexArray.h" +#include "../../lib/Point.h" +#include "../gui/CIntObject.h" + +VCMI_LIB_NAMESPACE_BEGIN +class CStack; +class Rect; +VCMI_LIB_NAMESPACE_END + +class BattleHero; +class CAnimation; +class Canvas; +class IImage; +class BattleInterface; + +/// Handles battlefield grid as well as rendering of background layer of battle interface +class BattleFieldController : public CIntObject +{ + BattleInterface & owner; + + std::shared_ptr background; + std::shared_ptr cellBorder; + std::shared_ptr cellUnitMovementHighlight; + std::shared_ptr cellUnitMaxMovementHighlight; + std::shared_ptr cellShade; + std::shared_ptr rangedFullDamageLimitImages; + std::shared_ptr shootingRangeLimitImages; + + std::shared_ptr attackCursors; + std::shared_ptr spellCursors; + + /// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack + std::unique_ptr backgroundWithHexes; + + /// direction which will be used to perform attack with current cursor position + Point currentAttackOriginPoint; + + /// hex currently under mouse hover + BattleHex hoveredHex; + + /// hexes to which currently active stack can move + BattleHexArray occupiableHexes; + + /// hexes that when in front of a unit cause it's amount box to move back + std::array stackCountOutsideHexes; + + void showHighlightedHex(Canvas & to, std::shared_ptr highlight, BattleHex hex, bool darkBorder); + + BattleHexArray getHighlightedHexesForActiveStack(); + BattleHexArray getMovementRangeForHoveredStack(); + BattleHexArray getHighlightedHexesForSpellRange(); + BattleHexArray getHighlightedHexesForMovementTarget(); + + // Range limit highlight helpers + + /// get all hexes within a certain distance of given hex + BattleHexArray getRangeHexes(BattleHex sourceHex, uint8_t distance); + + /// get only hexes at the limit of a range + BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray hexRange, uint8_t distanceToLimit); + + /// calculate if a hex is in range limit and return its index in range + bool IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit); + + /// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists + std::vector> getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray rangeHexes, BattleHexArray rangeLimitHexes); + + /// calculates what image to use as range limit, depending on the direction of neighbours + /// a mask is used internally to mark the directions of all neighbours + /// based on this mask the corresponding image is selected + std::vector> calculateRangeLimitHighlightImages(std::vector> hexesNeighbourDirections, std::shared_ptr limitImages); + + /// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes + void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights); + + void showBackground(Canvas & canvas); + void showBackgroundImage(Canvas & canvas); + void showBackgroundImageWithHexes(Canvas & canvas); + void showHighlightedHexes(Canvas & canvas); + void updateAccessibleHexes(); + + BattleHex getHexAtPosition(Point hoverPosition); + + /// Checks whether selected pixel is transparent, uses local coordinates of a hex + bool isPixelInHex(Point const & position); + size_t selectBattleCursor(BattleHex myNumber); + + void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; + void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override; + void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override; + void clickPressed(const Point & cursorPosition) override; + void showPopupWindow(const Point & cursorPosition) override; + void activate() override; + + void showAll(Canvas & to) override; + void show(Canvas & to) override; + void tick(uint32_t msPassed) override; + + bool receiveEvent(const Point & position, int eventType) const override; + +public: + BattleFieldController(BattleInterface & owner); + + void createHeroes(); + + void redrawBackgroundWithHexes(); + void renderBattlefield(Canvas & canvas); + + /// Returns position of hex relative to owner (BattleInterface) + Rect hexPositionLocal(BattleHex hex) const; + + /// Returns position of hex relative to game window + Rect hexPositionAbsolute(BattleHex hex) const; + + /// Returns ID of currently hovered hex or BattleHex::INVALID if none + BattleHex getHoveredHex(); + + /// Returns the currently hovered stack + const CStack* getHoveredStack(); + + /// returns true if selected tile can be attacked in melee by current stack + bool isTileAttackable(const BattleHex & number) const; + + /// returns true if stack should render its stack count image in default position - outside own hex + bool stackCountOutsideHex(const BattleHex & number) const; + + BattleHex::EDir selectAttackDirection(BattleHex myNumber); + + BattleHex fromWhichHexAttack(BattleHex myNumber); +}; diff --git a/client/battle/BattleInterface.cpp b/client/battle/BattleInterface.cpp index db02f9a2c..da8c39396 100644 --- a/client/battle/BattleInterface.cpp +++ b/client/battle/BattleInterface.cpp @@ -216,7 +216,7 @@ void BattleInterface::stackActivated(const CStack *stack) stacksController->stackActivated(stack); } -void BattleInterface::stackMoved(const CStack *stack, std::vector destHex, int distance, bool teleport) +void BattleInterface::stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport) { if (teleport) stacksController->stackTeleported(stack, destHex, distance); diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index a7c578ecf..219143b06 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -1,232 +1,232 @@ -/* - * BattleInterface.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "BattleConstants.h" -#include "../gui/CIntObject.h" -#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation -#include "../ConditionalWait.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class CCreatureSet; -class CGHeroInstance; -class CStack; -struct BattleResult; -struct BattleSpellCast; -struct CObstacleInstance; -struct SetStackEffect; -class BattleAction; -class CGTownInstance; -struct CatapultAttack; -struct BattleTriggerEffect; -struct BattleHex; -struct InfoAboutHero; -class ObstacleChanges; -class CPlayerBattleCallback; - -VCMI_LIB_NAMESPACE_END - -class BattleHero; -class Canvas; -class BattleResultWindow; -class StackQueue; -class CPlayerInterface; -struct BattleEffect; -class IImage; -class StackQueue; - -class BattleProjectileController; -class BattleSiegeController; -class BattleObstacleController; -class BattleFieldController; -class BattleRenderer; -class BattleWindow; -class BattleStacksController; -class BattleActionsController; -class BattleEffectsController; -class BattleConsole; - -/// Small struct which contains information about the id of the attacked stack, the damage dealt,... -struct StackAttackedInfo -{ - const CStack *defender; - const CStack *attacker; - - int64_t damageDealt; - uint32_t amountKilled; - SpellID spellEffect; - - bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack - bool killed; //if true, stack has been killed - bool rebirth; //if true, play rebirth animation after all - bool cloneKilled; - bool fireShield; -}; - -struct StackAttackInfo -{ - const CStack *attacker; - const CStack *defender; - std::vector< const CStack *> secondaryDefender; - - SpellID spellEffect; - BattleHex tile; - - bool indirectAttack; - bool lucky; - bool unlucky; - bool deathBlow; - bool lifeDrain; -}; - -/// Main class for battles, responsible for relaying information from server to various battle entities -class BattleInterface -{ - using AwaitingAnimationAction = std::function; - - struct AwaitingAnimationEvents { - AwaitingAnimationAction action; - EAnimationEvents event; - }; - - /// Conditional variables that are set depending on ongoing animations on the battlefield - ConditionalWait ongoingAnimationsState; - - /// List of events that are waiting to be triggered - std::vector awaitingEvents; - - /// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players - std::shared_ptr tacticianInterface; - - /// attacker interface, not null if attacker is human in our vcmiclient - std::shared_ptr attackerInt; - - /// defender interface, not null if attacker is human in our vcmiclient - std::shared_ptr defenderInt; - - /// if set to true, battle is still starting and waiting for intro sound to end / key press from player - bool battleOpeningDelayActive; - - /// ID of ongoing battle - BattleID battleID; - - void playIntroSoundAndUnlockInterface(); - void onIntroSoundPlayed(); -public: - /// copy of initial armies (for result window) - const CCreatureSet *army1; - const CCreatureSet *army2; - - std::shared_ptr windowObject; - std::shared_ptr console; - - /// currently active player interface - std::shared_ptr curInt; - - const CGHeroInstance *attackingHeroInstance; - const CGHeroInstance *defendingHeroInstance; - - bool tacticsMode; - ui32 round; - - std::unique_ptr projectilesController; - std::unique_ptr siegeController; - std::unique_ptr obstacleController; - std::unique_ptr fieldController; - std::unique_ptr stacksController; - std::unique_ptr actionsController; - std::unique_ptr effectsController; - - std::shared_ptr attackingHero; - std::shared_ptr defendingHero; - - bool openingPlaying() const; - void openingEnd(); - - bool makingTurn() const; - - BattleID getBattleID() const; - std::shared_ptr getBattle() const; - - BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); - ~BattleInterface(); - - void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player - void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all - void requestAutofightingAIToTakeAction(); - - void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE); - void sendCommand(BattleAction command, const CStack * actor = nullptr); - - const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell - - void showInterface(Canvas & to); - - void setHeroAnimation(BattleSide side, EHeroAnimType phase); - - void executeSpellCast(); //called when a hero casts a spell - - void appendBattleLog(const std::string & newEntry); - - void redrawBattlefield(); //refresh GUI after changing stack range / grid settings - CPlayerInterface *getCurrentPlayerInterface() const; - - void tacticNextStack(const CStack *current); - void tacticPhaseEnd(); - - void setBattleQueueVisibility(bool visible); - void setStickyHeroWindowsVisibility(bool visible); - void setStickyQuickSpellWindowVisibility(bool visible); - - void endNetwork(); - void executeStagedAnimations(); - void executeAnimationStage( EAnimationEvents event); - void onAnimationsStarted(); - void onAnimationsFinished(); - void waitForAnimations(); - bool hasAnimations(); - void checkForAnimations(); - void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action); - - //call-ins - void startAction(const BattleAction & action); - void stackReset(const CStack * stack); - void stackAdded(const CStack * stack); //new stack appeared on battlefield - void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled - void stackActivated(const CStack *stack); //active stack has been changed - void stackMoved(const CStack *stack, std::vector destHex, int distance, bool teleport); //stack with id number moved to destHex - void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked - void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest - void newRoundFirst(); - void newRound(); //called when round is ended; - void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls - void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed - void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell - void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks - void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook - - void displayBattleLog(const std::vector & battleLog); - - void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit); - void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation - void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation - void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation - - void endAction(const BattleAction & action); - - void obstaclePlaced(const std::vector> oi); - void obstacleRemoved(const std::vector & obstacles); - - void gateStateChanged(const EGateState state); - - const CGHeroInstance *currentHero() const; - InfoAboutHero enemyHero() const; -}; +/* + * BattleInterface.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "BattleConstants.h" +#include "../gui/CIntObject.h" +#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation +#include "../ConditionalWait.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CCreatureSet; +class CGHeroInstance; +class CStack; +struct BattleResult; +struct BattleSpellCast; +struct CObstacleInstance; +struct SetStackEffect; +class BattleAction; +class CGTownInstance; +struct CatapultAttack; +struct BattleTriggerEffect; +struct BattleHex; +struct InfoAboutHero; +class ObstacleChanges; +class CPlayerBattleCallback; + +VCMI_LIB_NAMESPACE_END + +class BattleHero; +class Canvas; +class BattleResultWindow; +class StackQueue; +class CPlayerInterface; +struct BattleEffect; +class IImage; +class StackQueue; + +class BattleProjectileController; +class BattleSiegeController; +class BattleObstacleController; +class BattleFieldController; +class BattleRenderer; +class BattleWindow; +class BattleStacksController; +class BattleActionsController; +class BattleEffectsController; +class BattleConsole; + +/// Small struct which contains information about the id of the attacked stack, the damage dealt,... +struct StackAttackedInfo +{ + const CStack *defender; + const CStack *attacker; + + int64_t damageDealt; + uint32_t amountKilled; + SpellID spellEffect; + + bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack + bool killed; //if true, stack has been killed + bool rebirth; //if true, play rebirth animation after all + bool cloneKilled; + bool fireShield; +}; + +struct StackAttackInfo +{ + const CStack *attacker; + const CStack *defender; + std::vector< const CStack *> secondaryDefender; + + SpellID spellEffect; + BattleHex tile; + + bool indirectAttack; + bool lucky; + bool unlucky; + bool deathBlow; + bool lifeDrain; +}; + +/// Main class for battles, responsible for relaying information from server to various battle entities +class BattleInterface +{ + using AwaitingAnimationAction = std::function; + + struct AwaitingAnimationEvents { + AwaitingAnimationAction action; + EAnimationEvents event; + }; + + /// Conditional variables that are set depending on ongoing animations on the battlefield + ConditionalWait ongoingAnimationsState; + + /// List of events that are waiting to be triggered + std::vector awaitingEvents; + + /// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players + std::shared_ptr tacticianInterface; + + /// attacker interface, not null if attacker is human in our vcmiclient + std::shared_ptr attackerInt; + + /// defender interface, not null if attacker is human in our vcmiclient + std::shared_ptr defenderInt; + + /// if set to true, battle is still starting and waiting for intro sound to end / key press from player + bool battleOpeningDelayActive; + + /// ID of ongoing battle + BattleID battleID; + + void playIntroSoundAndUnlockInterface(); + void onIntroSoundPlayed(); +public: + /// copy of initial armies (for result window) + const CCreatureSet *army1; + const CCreatureSet *army2; + + std::shared_ptr windowObject; + std::shared_ptr console; + + /// currently active player interface + std::shared_ptr curInt; + + const CGHeroInstance *attackingHeroInstance; + const CGHeroInstance *defendingHeroInstance; + + bool tacticsMode; + ui32 round; + + std::unique_ptr projectilesController; + std::unique_ptr siegeController; + std::unique_ptr obstacleController; + std::unique_ptr fieldController; + std::unique_ptr stacksController; + std::unique_ptr actionsController; + std::unique_ptr effectsController; + + std::shared_ptr attackingHero; + std::shared_ptr defendingHero; + + bool openingPlaying() const; + void openingEnd(); + + bool makingTurn() const; + + BattleID getBattleID() const; + std::shared_ptr getBattle() const; + + BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); + ~BattleInterface(); + + void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player + void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all + void requestAutofightingAIToTakeAction(); + + void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE); + void sendCommand(BattleAction command, const CStack * actor = nullptr); + + const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell + + void showInterface(Canvas & to); + + void setHeroAnimation(BattleSide side, EHeroAnimType phase); + + void executeSpellCast(); //called when a hero casts a spell + + void appendBattleLog(const std::string & newEntry); + + void redrawBattlefield(); //refresh GUI after changing stack range / grid settings + CPlayerInterface *getCurrentPlayerInterface() const; + + void tacticNextStack(const CStack *current); + void tacticPhaseEnd(); + + void setBattleQueueVisibility(bool visible); + void setStickyHeroWindowsVisibility(bool visible); + void setStickyQuickSpellWindowVisibility(bool visible); + + void endNetwork(); + void executeStagedAnimations(); + void executeAnimationStage( EAnimationEvents event); + void onAnimationsStarted(); + void onAnimationsFinished(); + void waitForAnimations(); + bool hasAnimations(); + void checkForAnimations(); + void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action); + + //call-ins + void startAction(const BattleAction & action); + void stackReset(const CStack * stack); + void stackAdded(const CStack * stack); //new stack appeared on battlefield + void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled + void stackActivated(const CStack *stack); //active stack has been changed + void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport); //stack with id number moved to destHex + void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked + void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest + void newRoundFirst(); + void newRound(); //called when round is ended; + void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls + void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed + void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell + void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks + void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook + + void displayBattleLog(const std::vector & battleLog); + + void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit); + void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation + void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation + void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation + + void endAction(const BattleAction & action); + + void obstaclePlaced(const std::vector> oi); + void obstacleRemoved(const std::vector & obstacles); + + void gateStateChanged(const EGateState state); + + const CGHeroInstance *currentHero() const; + InfoAboutHero enemyHero() const; +}; diff --git a/client/battle/BattleStacksController.cpp b/client/battle/BattleStacksController.cpp index 173925244..10f2eaabf 100644 --- a/client/battle/BattleStacksController.cpp +++ b/client/battle/BattleStacksController.cpp @@ -491,7 +491,7 @@ void BattleStacksController::stacksAreAttacked(std::vector at owner.waitForAnimations(); } -void BattleStacksController::stackTeleported(const CStack *stack, std::vector destHex, int distance) +void BattleStacksController::stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance) { assert(destHex.size() > 0); //owner.checkForAnimations(); // NOTE: at this point spellcast animations were added, but not executed @@ -508,7 +508,7 @@ void BattleStacksController::stackTeleported(const CStack *stack, std::vector destHex, int distance) +void BattleStacksController::stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance) { assert(destHex.size() > 0); owner.checkForAnimations(); diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 010602306..3f6862343 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -1,148 +1,149 @@ -/* - * BattleStacksController.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../render/ColorFilter.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleHex; -class BattleAction; -class CStack; -class CSpell; -class SpellID; -class Point; - -VCMI_LIB_NAMESPACE_END - -struct StackAttackedInfo; -struct StackAttackInfo; - -class ColorFilter; -class Canvas; -class BattleInterface; -class BattleAnimation; -class CreatureAnimation; -class BattleAnimation; -class BattleRenderer; -class IImage; - -struct BattleStackFilterEffect -{ - ColorFilter effect; - const CStack * target; - const CSpell * source; - bool persistent; -}; - -/// Class responsible for handling stacks in battle -/// Handles ordering of stacks animation -/// As well as rendering of stacks, their amount boxes -/// And any other effect applied to stacks -class BattleStacksController -{ - BattleInterface & owner; - - std::shared_ptr amountNormal; - std::shared_ptr amountNegative; - std::shared_ptr amountPositive; - std::shared_ptr amountEffNeutral; - - /// currently displayed animations - std::vector currentAnimations; - - /// currently active color effects on stacks, in order of their addition (which corresponds to their apply order) - std::vector stackFilterEffects; - - /// animations of creatures from fighting armies (order by BattleInfo's stacks' ID) - std::map> stackAnimation; - - /// //TODO: move it to battle callback - std::map stackFacingRight; - - /// Stacks have amount box hidden due to ongoing animations - std::set stackAmountBoxHidden; - - /// currently active stack; nullptr - no one - const CStack *activeStack; - - /// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation - std::vector mouseHoveredStacks; - - ///when animation is playing, we should wait till the end to make the next stack active; nullptr of none - const CStack *stackToActivate; - - /// for giving IDs for animations - ui32 animIDhelper; - - bool stackNeedsAmountBox(const CStack * stack) const; - void showStackAmountBox(Canvas & canvas, const CStack * stack); - BattleHex getStackCurrentPosition(const CStack * stack) const; - - std::shared_ptr getStackAmountBox(const CStack * stack); - - void removeExpiredColorFilters(); - - void initializeBattleAnimations(); - void tickFrameBattleAnimations(uint32_t msPassed); - - void updateBattleAnimations(uint32_t msPassed); - - std::vector selectHoveredStacks(); - - bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender); - -public: - BattleStacksController(BattleInterface & owner); - - bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const; - bool facingRight(const CStack * stack) const; - - void stackReset(const CStack * stack); - void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield - void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled - void stackActivated(const CStack *stack); //active stack has been changed - void stackMoved(const CStack *stack, std::vector destHex, int distance); //stack with id number moved to destHex - void stackTeleported(const CStack *stack, std::vector destHex, int distance); //stack with id number moved to destHex - void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked - void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest - - void startAction(const BattleAction & action); - void endAction(const BattleAction & action); - - void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack - - void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack - - void setActiveStack(const CStack *stack); - - void showAliveStack(Canvas & canvas, const CStack * stack); - void showStack(Canvas & canvas, const CStack * stack); - - void updateHoveredStacks(); - - void collectRenderableObjects(BattleRenderer & renderer); - - /// Adds new color filter effect targeting stack - /// Effect will last as long as stack is affected by specified spell (unless effect is persistent) - /// If effect from same (target, source) already exists, it will be updated - void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent); - void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims - - const CStack* getActiveStack() const; - const std::vector getHoveredStacksUnitIds() const; - - void tick(uint32_t msPassed); - - /// returns position of animation needed to place stack in specific hex - Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const; - - friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations -}; +/* + * BattleStacksController.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../render/ColorFilter.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleHex; +class BattleHexArray; +class BattleAction; +class CStack; +class CSpell; +class SpellID; +class Point; + +VCMI_LIB_NAMESPACE_END + +struct StackAttackedInfo; +struct StackAttackInfo; + +class ColorFilter; +class Canvas; +class BattleInterface; +class BattleAnimation; +class CreatureAnimation; +class BattleAnimation; +class BattleRenderer; +class IImage; + +struct BattleStackFilterEffect +{ + ColorFilter effect; + const CStack * target; + const CSpell * source; + bool persistent; +}; + +/// Class responsible for handling stacks in battle +/// Handles ordering of stacks animation +/// As well as rendering of stacks, their amount boxes +/// And any other effect applied to stacks +class BattleStacksController +{ + BattleInterface & owner; + + std::shared_ptr amountNormal; + std::shared_ptr amountNegative; + std::shared_ptr amountPositive; + std::shared_ptr amountEffNeutral; + + /// currently displayed animations + std::vector currentAnimations; + + /// currently active color effects on stacks, in order of their addition (which corresponds to their apply order) + std::vector stackFilterEffects; + + /// animations of creatures from fighting armies (order by BattleInfo's stacks' ID) + std::map> stackAnimation; + + /// //TODO: move it to battle callback + std::map stackFacingRight; + + /// Stacks have amount box hidden due to ongoing animations + std::set stackAmountBoxHidden; + + /// currently active stack; nullptr - no one + const CStack *activeStack; + + /// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation + std::vector mouseHoveredStacks; + + ///when animation is playing, we should wait till the end to make the next stack active; nullptr of none + const CStack *stackToActivate; + + /// for giving IDs for animations + ui32 animIDhelper; + + bool stackNeedsAmountBox(const CStack * stack) const; + void showStackAmountBox(Canvas & canvas, const CStack * stack); + BattleHex getStackCurrentPosition(const CStack * stack) const; + + std::shared_ptr getStackAmountBox(const CStack * stack); + + void removeExpiredColorFilters(); + + void initializeBattleAnimations(); + void tickFrameBattleAnimations(uint32_t msPassed); + + void updateBattleAnimations(uint32_t msPassed); + + std::vector selectHoveredStacks(); + + bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender); + +public: + BattleStacksController(BattleInterface & owner); + + bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const; + bool facingRight(const CStack * stack) const; + + void stackReset(const CStack * stack); + void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield + void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled + void stackActivated(const CStack *stack); //active stack has been changed + void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex + void stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex + void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked + void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest + + void startAction(const BattleAction & action); + void endAction(const BattleAction & action); + + void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack + + void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack + + void setActiveStack(const CStack *stack); + + void showAliveStack(Canvas & canvas, const CStack * stack); + void showStack(Canvas & canvas, const CStack * stack); + + void updateHoveredStacks(); + + void collectRenderableObjects(BattleRenderer & renderer); + + /// Adds new color filter effect targeting stack + /// Effect will last as long as stack is affected by specified spell (unless effect is persistent) + /// If effect from same (target, source) already exists, it will be updated + void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent); + void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims + + const CStack* getActiveStack() const; + const std::vector getHoveredStacksUnitIds() const; + + void tick(uint32_t msPassed); + + /// returns position of animation needed to place stack in specific hex + Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const; + + friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations +}; diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index d3c0a88c7..4554a44bb 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -34,14 +34,14 @@ #include "../../lib/mapping/CMapDefines.h" #include "../../lib/pathfinder/CGPathNode.h" -struct NeighborTilesInfo +struct neighbourTilesInfo { //567 //3 4 //012 std::bitset<8> d; - NeighborTilesInfo(IMapRendererContext & context, const int3 & pos) + neighbourTilesInfo(IMapRendererContext & context, const int3 & pos) { auto checkTile = [&](int dx, int dy) { @@ -340,9 +340,9 @@ void MapRendererFow::renderTile(IMapRendererContext & context, Canvas & target, { assert(!context.isVisible(coordinates)); - const NeighborTilesInfo neighborInfo(context, coordinates); + const neighbourTilesInfo neighbourInfo(context, coordinates); - int retBitmapID = neighborInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide + int retBitmapID = neighbourInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide if(retBitmapID < 0) { // generate a number that is predefined for each tile, @@ -367,8 +367,8 @@ uint8_t MapRendererFow::checksum(IMapRendererContext & context, const int3 & coo if (context.showSpellRange(coordinates)) return 0xff - 2; - const NeighborTilesInfo neighborInfo(context, coordinates); - int retBitmapID = neighborInfo.getBitmapID(); + const neighbourTilesInfo neighbourInfo(context, coordinates); + int retBitmapID = neighbourInfo.getBitmapID(); if(retBitmapID < 0) return 0xff - 1; return retBitmapID; @@ -738,9 +738,9 @@ MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & con return result; } - const NeighborTilesInfo neighborInfo(context, coordinates); + const neighbourTilesInfo neighbourInfo(context, coordinates); - if(!context.isVisible(coordinates) && neighborInfo.areAllHidden()) + if(!context.isVisible(coordinates) && neighbourInfo.areAllHidden()) { result[7] = rendererFow.checksum(context, coordinates); } @@ -769,9 +769,9 @@ void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, con return; } - const NeighborTilesInfo neighborInfo(context, coordinates); + const neighbourTilesInfo neighbourInfo(context, coordinates); - if(!context.isVisible(coordinates) && neighborInfo.areAllHidden()) + if(!context.isVisible(coordinates) && neighbourInfo.areAllHidden()) { rendererFow.renderTile(context, target, coordinates); } diff --git a/client/renderSDL/SDL_Extensions.cpp b/client/renderSDL/SDL_Extensions.cpp index 14671c2db..610c62f24 100644 --- a/client/renderSDL/SDL_Extensions.cpp +++ b/client/renderSDL/SDL_Extensions.cpp @@ -674,7 +674,7 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor switch (algorithm) { case EScalingAlgorithm::NEAREST: - xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); + xbrz::nearestneighbourScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); break; case EScalingAlgorithm::BILINEAR: xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); diff --git a/client/xBRZ/xbrz.cpp b/client/xBRZ/xbrz.cpp index 1eda5a46b..b6753de00 100644 --- a/client/xBRZ/xbrz.cpp +++ b/client/xBRZ/xbrz.cpp @@ -1273,10 +1273,10 @@ void xbrz::bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, } -void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, +void xbrz::nearestneighbourScale(const uint32_t* src, int srcWidth, int srcHeight, /**/ uint32_t* trg, int trgWidth, int trgHeight) { - nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + nearestneighbourScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), 0, trgHeight, [](uint32_t pix) { return pix; }); } diff --git a/client/xBRZ/xbrz.h b/client/xBRZ/xbrz.h index 4e646dba6..1643e41f1 100644 --- a/client/xBRZ/xbrz.h +++ b/client/xBRZ/xbrz.h @@ -69,7 +69,7 @@ void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, /**/ uint32_t* trg, int trgWidth, int trgHeight); -void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, +void nearestneighbourScale(const uint32_t* src, int srcWidth, int srcHeight, /**/ uint32_t* trg, int trgWidth, int trgHeight); diff --git a/client/xBRZ/xbrz_tools.h b/client/xBRZ/xbrz_tools.h index b8bb8aa0c..204056a38 100644 --- a/client/xBRZ/xbrz_tools.h +++ b/client/xBRZ/xbrz_tools.h @@ -67,9 +67,9 @@ void fillBlock(Pix* trg, int pitch /*[bytes]*/, Pix col, int blockWidth, int blo } -//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) +//nearest-neighbour (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) template -void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, +void nearestneighbourScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/, int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) { @@ -104,9 +104,9 @@ void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int sr } -//nearest-neighbor (going over source image - fast for upscaling, since source is read only once +//nearest-neighbour (going over source image - fast for upscaling, since source is read only once template -void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, +void nearestneighbourScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/, int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) { diff --git a/include/vstd/RNG.h b/include/vstd/RNG.h index 9a4e54a06..d42ba0a73 100644 --- a/include/vstd/RNG.h +++ b/include/vstd/RNG.h @@ -1,103 +1,104 @@ -/* - * RNG.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -VCMI_LIB_NAMESPACE_BEGIN - -namespace vstd -{ - -class DLL_LINKAGE RNG -{ -public: - virtual ~RNG() = default; - - /// Returns random number in range [lower, upper] - virtual int nextInt(int lower, int upper) = 0; - - /// Returns random number in range [lower, upper] - virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0; - - /// Returns random number in range [lower, upper] - virtual double nextDouble(double lower, double upper) = 0; - - /// Returns random number in range [0, upper] - virtual int nextInt(int upper) = 0; - - /// Returns random number in range [0, upper] - virtual int64_t nextInt64(int64_t upper) = 0; - - /// Returns random number in range [0, upper] - virtual double nextDouble(double upper) = 0; - - /// Generates an integer between 0 and the maximum value it can hold. - /// Should be only used for seeding other generators - virtual int nextInt() = 0; - - /// Returns integer using binomial distribution - /// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts - virtual int nextBinomialInt(int coinsCount, double coinChance) = 0; -}; - -} - -namespace RandomGeneratorUtil -{ - template - auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) - { - if(container.empty()) - throw std::runtime_error("Unable to select random item from empty container!"); - - return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); - } - - template - auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) - { - if(container.empty()) - throw std::runtime_error("Unable to select random item from empty container!"); - - return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); - } - - template - size_t nextItemWeighted(Container & container, vstd::RNG & rand) - { - assert(!container.empty()); - - int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0); - assert(totalWeight > 0); - - int64_t roll = rand.nextInt64(0, totalWeight - 1); - - for (size_t i = 0; i < container.size(); ++i) - { - roll -= container[i]; - if(roll < 0) - return i; - } - return container.size() - 1; - } - - template - void randomShuffle(std::vector & container, vstd::RNG & rand) - { - int64_t n = (container.end() - container.begin()); - - for(int64_t i = n-1; i>0; --i) - { - std::swap(container.begin()[i],container.begin()[rand.nextInt64(0, i)]); - } - } -} - -VCMI_LIB_NAMESPACE_END +/* + * RNG.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +namespace vstd +{ + +class DLL_LINKAGE RNG +{ +public: + virtual ~RNG() = default; + + /// Returns random number in range [lower, upper] + virtual int nextInt(int lower, int upper) = 0; + + /// Returns random number in range [lower, upper] + virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0; + + /// Returns random number in range [lower, upper] + virtual double nextDouble(double lower, double upper) = 0; + + /// Returns random number in range [0, upper] + virtual int nextInt(int upper) = 0; + + /// Returns random number in range [0, upper] + virtual int64_t nextInt64(int64_t upper) = 0; + + /// Returns random number in range [0, upper] + virtual double nextDouble(double upper) = 0; + + /// Generates an integer between 0 and the maximum value it can hold. + /// Should be only used for seeding other generators + virtual int nextInt() = 0; + + /// Returns integer using binomial distribution + /// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts + virtual int nextBinomialInt(int coinsCount, double coinChance) = 0; +}; + +} + +namespace RandomGeneratorUtil +{ + template + auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); + } + + template + auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); + } + + template + size_t nextItemWeighted(Container & container, vstd::RNG & rand) + { + assert(!container.empty()); + + int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0); + assert(totalWeight > 0); + + int64_t roll = rand.nextInt64(0, totalWeight - 1); + + for (size_t i = 0; i < container.size(); ++i) + { + roll -= container[i]; + if(roll < 0) + return i; + } + return container.size() - 1; + } + + template + void randomShuffle(Container & container, vstd::RNG & rand) + { + int64_t n = std::distance(container.begin(), container.end()); + + for(int64_t i = n - 1; i > 0; --i) + { + auto randIndex = rand.nextInt64(0, i); + std::swap(*(container.begin() + i), *(container.begin() + randIndex)); + } + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index ff265d2e5..7746aebb2 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -1,101 +1,101 @@ -/* - * BattleFieldHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include -#include "BattleFieldHandler.h" -#include "json/JsonBonus.h" - -VCMI_LIB_NAMESPACE_BEGIN - -std::shared_ptr BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) -{ - assert(identifier.find(':') == std::string::npos); - - auto info = std::make_shared(BattleField(index), identifier); - - info->modScope = scope; - info->graphics = ImagePath::fromJson(json["graphics"]); - info->icon = json["icon"].String(); - info->name = json["name"].String(); - for(const auto & b : json["bonuses"].Vector()) - { - auto bonus = JsonUtils::parseBonus(b); - - bonus->source = BonusSource::TERRAIN_OVERLAY; - bonus->sid = BonusSourceID(info->getId()); - bonus->duration = BonusDuration::ONE_BATTLE; - - info->bonuses.push_back(bonus); - } - - info->isSpecial = json["isSpecial"].Bool(); - for(auto node : json["impassableHexes"].Vector()) - info->impassableHexes.emplace_back(node.Integer()); - - info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]); - info->musicFilename = AudioPath::fromJson(json["music"]); - - return info; -} - -std::vector BattleFieldHandler::loadLegacyData() -{ - return std::vector(); -} - -const std::vector & BattleFieldHandler::getTypeNames() const -{ - static const auto types = std::vector { "battlefield" }; - - return types; -} - -int32_t BattleFieldInfo::getIndex() const -{ - return battlefield.getNum(); -} - -int32_t BattleFieldInfo::getIconIndex() const -{ - return iconIndex; -} - -std::string BattleFieldInfo::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string BattleFieldInfo::getModScope() const -{ - return modScope; -} - -std::string BattleFieldInfo::getNameTextID() const -{ - return name; -} - -std::string BattleFieldInfo::getNameTranslated() const -{ - return name; // TODO? -} - -void BattleFieldInfo::registerIcons(const IconRegistar & cb) const -{ - //cb(getIconIndex(), "BATTLEFIELD", icon); -} - -BattleField BattleFieldInfo::getId() const -{ - return battlefield; -} - -VCMI_LIB_NAMESPACE_END +/* + * BattleFieldHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include +#include "BattleFieldHandler.h" +#include "json/JsonBonus.h" + +VCMI_LIB_NAMESPACE_BEGIN + +std::shared_ptr BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + + auto info = std::make_shared(BattleField(index), identifier); + + info->modScope = scope; + info->graphics = ImagePath::fromJson(json["graphics"]); + info->icon = json["icon"].String(); + info->name = json["name"].String(); + for(const auto & b : json["bonuses"].Vector()) + { + auto bonus = JsonUtils::parseBonus(b); + + bonus->source = BonusSource::TERRAIN_OVERLAY; + bonus->sid = BonusSourceID(info->getId()); + bonus->duration = BonusDuration::ONE_BATTLE; + + info->bonuses.push_back(bonus); + } + + info->isSpecial = json["isSpecial"].Bool(); + for(auto node : json["impassableHexes"].Vector()) + info->impassableHexes.insert(node.Integer()); + + info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]); + info->musicFilename = AudioPath::fromJson(json["music"]); + + return info; +} + +std::vector BattleFieldHandler::loadLegacyData() +{ + return std::vector(); +} + +const std::vector & BattleFieldHandler::getTypeNames() const +{ + static const auto types = std::vector { "battlefield" }; + + return types; +} + +int32_t BattleFieldInfo::getIndex() const +{ + return battlefield.getNum(); +} + +int32_t BattleFieldInfo::getIconIndex() const +{ + return iconIndex; +} + +std::string BattleFieldInfo::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string BattleFieldInfo::getModScope() const +{ + return modScope; +} + +std::string BattleFieldInfo::getNameTextID() const +{ + return name; +} + +std::string BattleFieldInfo::getNameTranslated() const +{ + return name; // TODO? +} + +void BattleFieldInfo::registerIcons(const IconRegistar & cb) const +{ + //cb(getIconIndex(), "BATTLEFIELD", icon); +} + +BattleField BattleFieldInfo::getId() const +{ + return battlefield; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h index f95e01c89..583b350fd 100644 --- a/lib/BattleFieldHandler.h +++ b/lib/BattleFieldHandler.h @@ -1,80 +1,80 @@ -/* - * BattleFieldHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include "bonuses/Bonus.h" -#include "GameConstants.h" -#include "IHandlerBase.h" -#include "battle/BattleHex.h" -#include "filesystem/ResourcePath.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class BattleFieldInfo : public EntityT -{ -public: - BattleField battlefield; - std::vector> bonuses; - bool isSpecial; - ImagePath graphics; - std::string name; - std::string modScope; - std::string identifier; - std::string icon; - si32 iconIndex; - std::vector impassableHexes; - AudioPath openingSoundFilename; - AudioPath musicFilename; - - BattleFieldInfo() - : BattleFieldInfo(BattleField::NONE, "") - { - } - - BattleFieldInfo(BattleField battlefield, std::string identifier): - isSpecial(false), - battlefield(battlefield), - identifier(identifier), - iconIndex(battlefield.getNum()), - name(identifier) - { - } - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - std::string getNameTextID() const override; - std::string getNameTranslated() const override; - void registerIcons(const IconRegistar & cb) const override; - BattleField getId() const override; -}; - -class DLL_LINKAGE BattleFieldService : public EntityServiceT -{ -public: -}; - -class BattleFieldHandler : public CHandlerBase -{ -public: - std::shared_ptr loadFromJson( - const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - const std::vector & getTypeNames() const override; - std::vector loadLegacyData() override; -}; - -VCMI_LIB_NAMESPACE_END +/* + * BattleFieldHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include +#include +#include "bonuses/Bonus.h" +#include "GameConstants.h" +#include "IHandlerBase.h" +#include "battle/BattleHexArray.h" +#include "filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class BattleFieldInfo : public EntityT +{ +public: + BattleField battlefield; + std::vector> bonuses; + bool isSpecial; + ImagePath graphics; + std::string name; + std::string modScope; + std::string identifier; + std::string icon; + si32 iconIndex; + BattleHexArray impassableHexes; + AudioPath openingSoundFilename; + AudioPath musicFilename; + + BattleFieldInfo() + : BattleFieldInfo(BattleField::NONE, "") + { + } + + BattleFieldInfo(BattleField battlefield, std::string identifier): + isSpecial(false), + battlefield(battlefield), + identifier(identifier), + iconIndex(battlefield.getNum()), + name(identifier) + { + } + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; + void registerIcons(const IconRegistar & cb) const override; + BattleField getId() const override; +}; + +class DLL_LINKAGE BattleFieldService : public EntityServiceT +{ +public: +}; + +class BattleFieldHandler : public CHandlerBase +{ +public: + std::shared_ptr loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/CGameInterface.cpp b/lib/CGameInterface.cpp index 8581a2f6d..852e56748 100644 --- a/lib/CGameInterface.cpp +++ b/lib/CGameInterface.cpp @@ -204,7 +204,7 @@ void CAdventureAI::battleObstaclesChanged(const BattleID & battleID, const std:: battleAI->battleObstaclesChanged(battleID, obstacles); } -void CAdventureAI::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) +void CAdventureAI::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) { battleAI->battleStackMoved(battleID, stack, dest, distance, teleport); } diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index a0896014a..fc1559bda 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -151,7 +151,7 @@ public: void actionFinished(const BattleID & battleID, const BattleAction &action) override; void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; void battleObstaclesChanged(const BattleID & battleID, const std::vector & obstacles) override; - void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport) override; + void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bd7d3ea1d..6be9b534f 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -47,6 +47,7 @@ set(lib_MAIN_SRCS battle/BattleAction.cpp battle/BattleAttackInfo.cpp battle/BattleHex.cpp + battle/BattleHexArray.cpp battle/BattleInfo.cpp battle/BattleLayout.cpp battle/BattleProxy.cpp @@ -413,6 +414,7 @@ set(lib_MAIN_HEADERS battle/BattleAction.h battle/BattleAttackInfo.h battle/BattleHex.h + battle/BattleHexArray.h battle/BattleInfo.h battle/BattleLayout.h battle/BattleSide.h diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 1819e57df..9d36c9078 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -242,10 +242,10 @@ void CStack::prepareAttacked(BattleStackAttacked & bsa, vstd::RNG & rand, const bsa.newState.operation = UnitChanges::EOperation::RESET_STATE; } -std::vector CStack::meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos) +BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos, BattleHex defenderPos) { int mask = 0; - std::vector res; + BattleHexArray res; if (!attackerPos.isValid()) attackerPos = attacker->getPosition(); @@ -260,7 +260,7 @@ std::vector CStack::meleeAttackHexes(const battle::Unit * attacker, c if((mask & 1) == 0) { mask |= 1; - res.push_back(defenderPos); + res.insert(defenderPos); } } if (attacker->doubleWide() //back <=> front @@ -269,7 +269,7 @@ std::vector CStack::meleeAttackHexes(const battle::Unit * attacker, c if((mask & 1) == 0) { mask |= 1; - res.push_back(defenderPos); + res.insert(defenderPos); } } if (defender->doubleWide()//front <=> back @@ -278,7 +278,7 @@ std::vector CStack::meleeAttackHexes(const battle::Unit * attacker, c if((mask & 2) == 0) { mask |= 2; - res.push_back(otherDefenderPos); + res.insert(otherDefenderPos); } } if (defender->doubleWide() && attacker->doubleWide()//back <=> back @@ -287,7 +287,7 @@ std::vector CStack::meleeAttackHexes(const battle::Unit * attacker, c if((mask & 2) == 0) { mask |= 2; - res.push_back(otherDefenderPos); + res.insert(otherDefenderPos); } } diff --git a/lib/CStack.h b/lib/CStack.h index d339eba40..23f22cf4f 100644 --- a/lib/CStack.h +++ b/lib/CStack.h @@ -60,7 +60,7 @@ public: std::vector activeSpells() const; //returns vector of active spell IDs sorted by time of cast const CGHeroInstance * getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise - static std::vector meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID); + static BattleHexArray meleeAttackHexes(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID); static bool isMeleeAttackPossible(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPos = BattleHex::INVALID, BattleHex defenderPos = BattleHex::INVALID); BattleHex::EDir destShiftDir() const; @@ -146,4 +146,4 @@ private: const BattleInfo * battle; //do not serialize }; -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index ca7d5e86f..e90e665f6 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -10,7 +10,7 @@ #pragma once #include "networkPacks/EInfoWindowMode.h" -#include "battle/BattleHex.h" +#include "battle/BattleHexArray.h" #include "GameConstants.h" #include "int3.h" @@ -63,7 +63,7 @@ public: virtual void battleNewRoundFirst(const BattleID & battleID){}; //called at the beginning of each turn before changes are applied; virtual void battleNewRound(const BattleID & battleID){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn virtual void battleLogMessage(const BattleID & battleID, const std::vector & lines){}; - virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector dest, int distance, bool teleport){}; + virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport){}; virtual void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc){}; virtual void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse){};//called when a specific effect is set to stacks virtual void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte){}; //called for various one-shot effects diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index b78f048c9..c438773ca 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -1,130 +1,129 @@ -/* - * ObstacleHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "ObstacleHandler.h" -#include "BattleFieldHandler.h" -#include "json/JsonNode.h" -#include "modding/IdentifierStorage.h" -#include "VCMI_Lib.h" - -VCMI_LIB_NAMESPACE_BEGIN - -int32_t ObstacleInfo::getIndex() const -{ - return obstacle.getNum(); -} - -int32_t ObstacleInfo::getIconIndex() const -{ - return iconIndex; -} - -std::string ObstacleInfo::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string ObstacleInfo::getModScope() const -{ - return modScope; -} - -std::string ObstacleInfo::getNameTranslated() const -{ - return identifier; -} - -std::string ObstacleInfo::getNameTextID() const -{ - return identifier; // TODO? -} - -void ObstacleInfo::registerIcons(const IconRegistar & cb) const -{ -} - -Obstacle ObstacleInfo::getId() const -{ - return obstacle; -} - -std::vector ObstacleInfo::getBlocked(BattleHex hex) const -{ - std::vector ret; - if(isAbsoluteObstacle) - { - assert(!hex.isValid()); - range::copy(blockedTiles, std::back_inserter(ret)); - return ret; - } - - for(int offset : blockedTiles) - { - BattleHex toBlock = hex + offset; - if((hex.getY() & 1) && !(toBlock.getY() & 1)) - toBlock += BattleHex::LEFT; - - if(!toBlock.isValid()) - logGlobal->error("Misplaced obstacle!"); - else - ret.push_back(toBlock); - } - - return ret; -} - -bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const -{ - const auto * bgInfo = battlefield.getInfo(); - - if(bgInfo->isSpecial) - return vstd::contains(allowedSpecialBfields, bgInfo->identifier); - - return vstd::contains(allowedTerrains, terrainType); -} - -std::shared_ptr ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) -{ - assert(identifier.find(':') == std::string::npos); - - auto info = std::make_shared(Obstacle(index), identifier); - - info->modScope = scope; - info->animation = AnimationPath::fromJson(json["animation"]); - info->width = json["width"].Integer(); - info->height = json["height"].Integer(); - for(const auto & t : json["allowedTerrains"].Vector()) - { - VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){ - info->allowedTerrains.emplace_back(identifier); - }); - } - for(const auto & t : json["specialBattlefields"].Vector()) - - info->allowedSpecialBfields.emplace_back(t.String()); - info->blockedTiles = json["blockedTiles"].convertTo>(); - info->isAbsoluteObstacle = json["absolute"].Bool(); - info->isForegroundObstacle = json["foreground"].Bool(); - - return info; -} - -std::vector ObstacleHandler::loadLegacyData() -{ - return {}; -} - -const std::vector & ObstacleHandler::getTypeNames() const -{ - static const std::vector types = { "obstacle" }; - return types; -} - -VCMI_LIB_NAMESPACE_END +/* + * ObstacleHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "ObstacleHandler.h" +#include "BattleFieldHandler.h" +#include "json/JsonNode.h" +#include "modding/IdentifierStorage.h" +#include "VCMI_Lib.h" + +VCMI_LIB_NAMESPACE_BEGIN + +int32_t ObstacleInfo::getIndex() const +{ + return obstacle.getNum(); +} + +int32_t ObstacleInfo::getIconIndex() const +{ + return iconIndex; +} + +std::string ObstacleInfo::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string ObstacleInfo::getModScope() const +{ + return modScope; +} + +std::string ObstacleInfo::getNameTranslated() const +{ + return identifier; +} + +std::string ObstacleInfo::getNameTextID() const +{ + return identifier; // TODO? +} + +void ObstacleInfo::registerIcons(const IconRegistar & cb) const +{ +} + +Obstacle ObstacleInfo::getId() const +{ + return obstacle; +} + +BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const +{ + if(isAbsoluteObstacle) + { + assert(!hex.isValid()); + return BattleHexArray(blockedTiles); + } + + BattleHexArray ret; + for(int offset : blockedTiles) + { + BattleHex toBlock = hex + offset; + if((hex.getY() & 1) && !(toBlock.getY() & 1)) + toBlock += BattleHex::LEFT; + + if(!toBlock.isValid()) + logGlobal->error("Misplaced obstacle!"); + else + ret.insert(toBlock); + } + + return ret; +} + +bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const +{ + const auto * bgInfo = battlefield.getInfo(); + + if(bgInfo->isSpecial) + return vstd::contains(allowedSpecialBfields, bgInfo->identifier); + + return vstd::contains(allowedTerrains, terrainType); +} + +std::shared_ptr ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + + auto info = std::make_shared(Obstacle(index), identifier); + + info->modScope = scope; + info->animation = AnimationPath::fromJson(json["animation"]); + info->width = json["width"].Integer(); + info->height = json["height"].Integer(); + for(const auto & t : json["allowedTerrains"].Vector()) + { + VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){ + info->allowedTerrains.emplace_back(identifier); + }); + } + for(const auto & t : json["specialBattlefields"].Vector()) + + info->allowedSpecialBfields.emplace_back(t.String()); + info->blockedTiles = json["blockedTiles"].convertTo>(); + info->isAbsoluteObstacle = json["absolute"].Bool(); + info->isForegroundObstacle = json["foreground"].Bool(); + + return info; +} + +std::vector ObstacleHandler::loadLegacyData() +{ + return {}; +} + +const std::vector & ObstacleHandler::getTypeNames() const +{ + static const std::vector types = { "obstacle" }; + return types; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/ObstacleHandler.h b/lib/ObstacleHandler.h index fe4dbdac0..7c81127a4 100644 --- a/lib/ObstacleHandler.h +++ b/lib/ObstacleHandler.h @@ -1,79 +1,79 @@ -/* - * ObstacleHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include "GameConstants.h" -#include "IHandlerBase.h" -#include "battle/BattleHex.h" -#include "filesystem/ResourcePath.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class DLL_LINKAGE ObstacleInfo : public EntityT -{ -public: - ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false) - {} - - ObstacleInfo(Obstacle obstacle, std::string identifier) - : obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false) - { - } - - Obstacle obstacle; - si32 iconIndex; - std::string modScope; - std::string identifier; - AudioPath appearSound; - AnimationPath appearAnimation; - AnimationPath animation; - std::vector allowedTerrains; - std::vector allowedSpecialBfields; - - bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same - bool isForegroundObstacle; - si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) - si32 height; - std::vector blockedTiles; //offsets relative to obstacle position (that is its left bottom corner) - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - std::string getNameTranslated() const override; - std::string getNameTextID() const override; - void registerIcons(const IconRegistar & cb) const override; - Obstacle getId() const override; - - std::vector getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' - - bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const; -}; - -class DLL_LINKAGE ObstacleService : public EntityServiceT -{ -public: -}; - -class ObstacleHandler: public CHandlerBase -{ -public: - std::shared_ptr loadFromJson(const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - const std::vector & getTypeNames() const override; - std::vector loadLegacyData() override; -}; - -VCMI_LIB_NAMESPACE_END +/* + * ObstacleHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include +#include +#include "GameConstants.h" +#include "IHandlerBase.h" +#include "battle/BattleHexArray.h" +#include "filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE ObstacleInfo : public EntityT +{ +public: + ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false) + {} + + ObstacleInfo(Obstacle obstacle, std::string identifier) + : obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false) + { + } + + Obstacle obstacle; + si32 iconIndex; + std::string modScope; + std::string identifier; + AudioPath appearSound; + AnimationPath appearAnimation; + AnimationPath animation; + std::vector allowedTerrains; + std::vector allowedSpecialBfields; + + bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same + bool isForegroundObstacle; + si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) + si32 height; + std::vector blockedTiles; //offsets relative to obstacle position (that is its left bottom corner) + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + std::string getNameTranslated() const override; + std::string getNameTextID() const override; + void registerIcons(const IconRegistar & cb) const override; + Obstacle getId() const override; + + BattleHexArray getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' + + bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const; +}; + +class DLL_LINKAGE ObstacleService : public EntityServiceT +{ +public: +}; + +class ObstacleHandler: public CHandlerBase +{ +public: + std::shared_ptr loadFromJson(const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index 581cf538f..197a0451c 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -1,245 +1,138 @@ -/* - * BattleHex.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "BattleHex.h" - -VCMI_LIB_NAMESPACE_BEGIN - -BattleHex::BattleHex() : hex(INVALID) {} - -BattleHex::BattleHex(si16 _hex) : hex(_hex) {} - -BattleHex::BattleHex(si16 x, si16 y) -{ - setXY(x, y); -} - -BattleHex::BattleHex(std::pair xy) -{ - setXY(xy); -} - -BattleHex::operator si16() const -{ - return hex; -} - -bool BattleHex::isValid() const -{ - return hex >= 0 && hex < GameConstants::BFIELD_SIZE; -} - -bool BattleHex::isAvailable() const -{ - return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH-1; -} - -void BattleHex::setX(si16 x) -{ - setXY(x, getY()); -} - -void BattleHex::setY(si16 y) -{ - setXY(getX(), y); -} - -void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) -{ - if(hasToBeValid) - { - if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) - throw std::runtime_error("Valid hex required"); - } - - hex = x + y * GameConstants::BFIELD_WIDTH; -} - -void BattleHex::setXY(std::pair xy) -{ - setXY(xy.first, xy.second); -} - -si16 BattleHex::getX() const -{ - return hex % GameConstants::BFIELD_WIDTH; -} - -si16 BattleHex::getY() const -{ - return hex / GameConstants::BFIELD_WIDTH; -} - -std::pair BattleHex::getXY() const -{ - return std::make_pair(getX(), getY()); -} - -BattleHex& BattleHex::moveInDirection(EDir dir, bool hasToBeValid) -{ - si16 x = getX(); - si16 y = getY(); - switch(dir) - { - case TOP_LEFT: - setXY((y%2) ? x-1 : x, y-1, hasToBeValid); - break; - case TOP_RIGHT: - setXY((y%2) ? x : x+1, y-1, hasToBeValid); - break; - case RIGHT: - setXY(x+1, y, hasToBeValid); - break; - case BOTTOM_RIGHT: - setXY((y%2) ? x : x+1, y+1, hasToBeValid); - break; - case BOTTOM_LEFT: - setXY((y%2) ? x-1 : x, y+1, hasToBeValid); - break; - case LEFT: - setXY(x-1, y, hasToBeValid); - break; - case NONE: - break; - default: - throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); - break; - } - return *this; -} - -BattleHex &BattleHex::operator+=(BattleHex::EDir dir) -{ - return moveInDirection(dir); -} - -BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const -{ - BattleHex result(hex); - result.moveInDirection(dir, hasToBeValid); - return result; -} - -BattleHex BattleHex::operator+(BattleHex::EDir dir) const -{ - return cloneInDirection(dir); -} - -std::vector BattleHex::neighbouringTiles() const -{ - std::vector ret; - ret.reserve(6); - for(auto dir : hexagonalDirections()) - checkAndPush(cloneInDirection(dir, false), ret); - return ret; -} - -std::vector BattleHex::allNeighbouringTiles() const -{ - std::vector ret; - ret.resize(6); - - for(auto dir : hexagonalDirections()) - ret[dir] = cloneInDirection(dir, false); - - return ret; -} - -BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) -{ - for(auto dir : hexagonalDirections()) - if(hex2 == hex1.cloneInDirection(dir, false)) - return dir; - return NONE; -} - -uint8_t BattleHex::getDistance(BattleHex hex1, BattleHex hex2) -{ - int y1 = hex1.getY(); - int y2 = hex2.getY(); - - // FIXME: why there was * 0.5 instead of / 2? - int x1 = static_cast(hex1.getX() + y1 / 2); - int x2 = static_cast(hex2.getX() + y2 / 2); - - int xDst = x2 - x1; - int yDst = y2 - y1; - - if ((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0)) - return std::max(std::abs(xDst), std::abs(yDst)); - - return std::abs(xDst) + std::abs(yDst); -} - -void BattleHex::checkAndPush(BattleHex tile, std::vector & ret) -{ - if(tile.isAvailable()) - ret.push_back(tile); -} - -BattleHex BattleHex::getClosestTile(BattleSide side, BattleHex initialPos, std::set & possibilities) -{ - std::vector sortedTiles (possibilities.begin(), possibilities.end()); //set can't be sorted properly :( - BattleHex initialHex = BattleHex(initialPos); - auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool - { - return initialHex.getDistance (initialHex, left) < initialHex.getDistance (initialHex, right); - }; - boost::sort (sortedTiles, compareDistance); //closest tiles at front - int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away - auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool - { - return closestDistance < here.getDistance (initialPos, here); - }; - vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting - auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool - { - if(left.getX() != right.getX()) - { - if(side == BattleSide::ATTACKER) - return left.getX() > right.getX(); //find furthest right - else - return left.getX() < right.getX(); //find furthest left - } - else - { - //Prefer tiles in the same row. - return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); - } - }; - boost::sort (sortedTiles, compareHorizontal); - return sortedTiles.front(); -} - -std::ostream & operator<<(std::ostream & os, const BattleHex & hex) -{ - return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); -} - -static BattleHex::NeighbouringTilesCache calculateNeighbouringTiles() -{ - BattleHex::NeighbouringTilesCache ret; - ret.resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - auto hexes = BattleHex(hex).neighbouringTiles(); - - size_t index = 0; - for(auto neighbour : hexes) - ret[hex].at(index++) = neighbour; - } - - return ret; -} - -const BattleHex::NeighbouringTilesCache BattleHex::neighbouringTilesCache = calculateNeighbouringTiles(); - -VCMI_LIB_NAMESPACE_END +/* + * BattleHex.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "BattleHex.h" + +VCMI_LIB_NAMESPACE_BEGIN + +BattleHex::BattleHex() : hex(INVALID) {} + +BattleHex::BattleHex(si16 _hex) : hex(_hex) {} + +BattleHex::BattleHex(si16 x, si16 y) +{ + setXY(x, y); +} + +BattleHex::BattleHex(std::pair xy) +{ + setXY(xy); +} + +BattleHex::operator si16() const +{ + return hex; +} + +void BattleHex::setX(si16 x) +{ + setXY(x, getY()); +} + +void BattleHex::setY(si16 y) +{ + setXY(getX(), y); +} + +void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) +{ + if(hasToBeValid) + { + if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) + throw std::runtime_error("Valid hex required"); + } + + hex = x + y * GameConstants::BFIELD_WIDTH; +} + +void BattleHex::setXY(std::pair xy) +{ + setXY(xy.first, xy.second); +} + +si16 BattleHex::getX() const +{ + return hex % GameConstants::BFIELD_WIDTH; +} + +si16 BattleHex::getY() const +{ + return hex / GameConstants::BFIELD_WIDTH; +} + +std::pair BattleHex::getXY() const +{ + return std::make_pair(getX(), getY()); +} + +BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid) +{ + si16 x = getX(); + si16 y = getY(); + switch(dir) + { + case TOP_LEFT: + setXY((y%2) ? x-1 : x, y-1, hasToBeValid); + break; + case TOP_RIGHT: + setXY((y%2) ? x : x+1, y-1, hasToBeValid); + break; + case RIGHT: + setXY(x+1, y, hasToBeValid); + break; + case BOTTOM_RIGHT: + setXY((y%2) ? x : x+1, y+1, hasToBeValid); + break; + case BOTTOM_LEFT: + setXY((y%2) ? x-1 : x, y+1, hasToBeValid); + break; + case LEFT: + setXY(x-1, y, hasToBeValid); + break; + case NONE: + break; + default: + throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); + break; + } + return *this; +} + +BattleHex & BattleHex::operator+=(BattleHex::EDir dir) +{ + return moveInDirection(dir); +} + +BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const +{ + BattleHex result(hex); + result.moveInDirection(dir, hasToBeValid); + return result; +} + +BattleHex BattleHex::operator+(BattleHex::EDir dir) const +{ + return cloneInDirection(dir); +} + +BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) +{ + for(auto dir : hexagonalDirections()) + if(hex2 == hex1.cloneInDirection(dir, false)) + return dir; + return NONE; +} + +std::ostream & operator<<(std::ostream & os, const BattleHex & hex) +{ + return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index 0f1dc37e4..e86a5caf1 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -1,117 +1,127 @@ -/* - * BattleHex.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "BattleSide.h" - -VCMI_LIB_NAMESPACE_BEGIN - -//TODO: change to enum class - -namespace GameConstants -{ - const int BFIELD_WIDTH = 17; - const int BFIELD_HEIGHT = 11; - const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; -} - -// for battle stacks' positions -struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design -{ - // helpers for siege - static constexpr si16 CASTLE_CENTRAL_TOWER = -2; - static constexpr si16 CASTLE_BOTTOM_TOWER = -3; - static constexpr si16 CASTLE_UPPER_TOWER = -4; - - // hexes for interaction with heroes - static constexpr si16 HERO_ATTACKER = 0; - static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1; - - // helpers for rendering - static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits::min(); - static constexpr si16 HEX_AFTER_ALL = std::numeric_limits::max(); - - static constexpr si16 DESTRUCTIBLE_WALL_1 = 29; - static constexpr si16 DESTRUCTIBLE_WALL_2 = 78; - static constexpr si16 DESTRUCTIBLE_WALL_3 = 130; - static constexpr si16 DESTRUCTIBLE_WALL_4 = 182; - static constexpr si16 GATE_BRIDGE = 94; - static constexpr si16 GATE_OUTER = 95; - static constexpr si16 GATE_INNER = 96; - - si16 hex; - static constexpr si16 INVALID = -1; - enum EDir - { - NONE = -1, - - TOP_LEFT, - TOP_RIGHT, - RIGHT, - BOTTOM_RIGHT, - BOTTOM_LEFT, - LEFT, - - //Note: unused by BattleHex class, used by other code - TOP, - BOTTOM - }; - - BattleHex(); - BattleHex(si16 _hex); - BattleHex(si16 x, si16 y); - BattleHex(std::pair xy); - operator si16() const; - bool isValid() const; - bool isAvailable() const; //valid position not in first or last column - void setX(si16 x); - void setY(si16 y); - void setXY(si16 x, si16 y, bool hasToBeValid = true); - void setXY(std::pair xy); - si16 getX() const; - si16 getY() const; - std::pair getXY() const; - BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); - BattleHex& operator+=(EDir dir); - BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const; - BattleHex operator+(EDir dir) const; - - /// returns all valid neighbouring tiles - std::vector neighbouringTiles() const; - - /// returns all tiles, unavailable tiles will be set as invalid - /// order of returned tiles matches EDir enim - std::vector allNeighbouringTiles() const; - - static EDir mutualPosition(BattleHex hex1, BattleHex hex2); - static uint8_t getDistance(BattleHex hex1, BattleHex hex2); - static void checkAndPush(BattleHex tile, std::vector & ret); - static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, std::set & possibilities); //TODO: vector or set? copying one to another is bad - - template - void serialize(Handler &h) - { - h & hex; - } - - using NeighbouringTiles = std::array; - using NeighbouringTilesCache = std::vector; - - static const NeighbouringTilesCache neighbouringTilesCache; -private: - //Constexpr defined array with all directions used in battle - static constexpr auto hexagonalDirections() { - return std::array{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; - } -}; - -DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); - -VCMI_LIB_NAMESPACE_END +/* + * BattleHex.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "BattleSide.h" + +VCMI_LIB_NAMESPACE_BEGIN + +//TODO: change to enum class + +namespace GameConstants +{ + const int BFIELD_WIDTH = 17; + const int BFIELD_HEIGHT = 11; + const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; +} + +// for battle stacks' positions +struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design +{ + // helpers for siege + static constexpr si16 CASTLE_CENTRAL_TOWER = -2; + static constexpr si16 CASTLE_BOTTOM_TOWER = -3; + static constexpr si16 CASTLE_UPPER_TOWER = -4; + + // hexes for interaction with heroes + static constexpr si16 HERO_ATTACKER = 0; + static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1; + + // helpers for rendering + static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits::min(); + static constexpr si16 HEX_AFTER_ALL = std::numeric_limits::max(); + + static constexpr si16 DESTRUCTIBLE_WALL_1 = 29; + static constexpr si16 DESTRUCTIBLE_WALL_2 = 78; + static constexpr si16 DESTRUCTIBLE_WALL_3 = 130; + static constexpr si16 DESTRUCTIBLE_WALL_4 = 182; + static constexpr si16 GATE_BRIDGE = 94; + static constexpr si16 GATE_OUTER = 95; + static constexpr si16 GATE_INNER = 96; + + si16 hex; + static constexpr si16 INVALID = -1; + enum EDir + { + NONE = -1, + + TOP_LEFT, + TOP_RIGHT, + RIGHT, + BOTTOM_RIGHT, + BOTTOM_LEFT, + LEFT, + + //Note: unused by BattleHex class, used by other code + TOP, + BOTTOM + }; + + BattleHex(); + BattleHex(si16 _hex); + BattleHex(si16 x, si16 y); + BattleHex(std::pair xy); + operator si16() const; + inline bool isValid() const + { + return hex >= 0 && hex < GameConstants::BFIELD_SIZE; + } + + bool isAvailable() const //valid position not in first or last column + { + return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; + } + + void setX(si16 x); + void setY(si16 y); + void setXY(si16 x, si16 y, bool hasToBeValid = true); + void setXY(std::pair xy); + si16 getX() const; + si16 getY() const; + std::pair getXY() const; + BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); + BattleHex& operator+=(EDir dir); + BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const; + BattleHex operator+(EDir dir) const; + + static EDir mutualPosition(BattleHex hex1, BattleHex hex2); + static uint8_t getDistance(BattleHex hex1, BattleHex hex2) + { + int y1 = hex1.getY(); + int y2 = hex2.getY(); + + // FIXME: why there was * 0.5 instead of / 2? + int x1 = static_cast(hex1.getX() + y1 / 2); + int x2 = static_cast(hex2.getX() + y2 / 2); + + int xDst = x2 - x1; + int yDst = y2 - y1; + + if((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0)) + return std::max(std::abs(xDst), std::abs(yDst)); + + return std::abs(xDst) + std::abs(yDst); + } + + template + void serialize(Handler &h) + { + h & hex; + } + + //Constexpr defined array with all directions used in battle + static constexpr auto hexagonalDirections() { + return std::array{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; + } +}; + +DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp new file mode 100644 index 000000000..ddfa23609 --- /dev/null +++ b/lib/battle/BattleHexArray.cpp @@ -0,0 +1,103 @@ +/* + * BattleHexArray.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "BattleHexArray.h" + +VCMI_LIB_NAMESPACE_BEGIN + +BattleHexArray::BattleHexArray(std::initializer_list initList) noexcept + : BattleHexArray() +{ + for(auto hex : initList) + { + insert(hex); + } +} + +BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) +{ + BattleHex initialHex = BattleHex(initialPos); + auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool + { + return initialHex.getDistance(initialHex, left) < initialHex.getDistance(initialHex, right); + }; + BattleHexArray sortedTiles(*this); + boost::sort(sortedTiles, compareDistance); //closest tiles at front + int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away + auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool + { + return closestDistance < here.getDistance(initialPos, here); + }; + vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting + auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool + { + if(left.getX() != right.getX()) + { + if(side == BattleSide::ATTACKER) + return left.getX() > right.getX(); //find furthest right + else + return left.getX() < right.getX(); //find furthest left + } + else + { + //Prefer tiles in the same row. + return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); + } + }; + boost::sort(sortedTiles, compareHorizontal); + return sortedTiles.front(); +} + +void BattleHexArray::merge(const BattleHexArray & other) noexcept +{ + for(auto hex : other) + { + insert(hex); + } +} + +void BattleHexArray::erase(iterator first, iterator last) noexcept +{ + for(auto it = first; it != last && it != internalStorage.end(); ++it) + { + presenceFlags[*it] = 0; + } + + internalStorage.erase(first, last); +} + +void BattleHexArray::clear() noexcept +{ + for(auto hex : internalStorage) + presenceFlags[hex] = 0; + + internalStorage.clear(); +} + +static BattleHexArray::NeighbouringTilesCache calculateNeighbouringTiles() +{ + BattleHexArray::NeighbouringTilesCache ret; + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + auto hexes = BattleHexArray::generateNeighbouringTiles(hex); + + size_t index = 0; + for(auto neighbour : hexes) + ret[hex].at(index++) = neighbour; + } + + return ret; +} + +const BattleHexArray::NeighbouringTilesCache BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles(); + +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h new file mode 100644 index 000000000..81eb217c7 --- /dev/null +++ b/lib/battle/BattleHexArray.h @@ -0,0 +1,300 @@ +/* + * BattleHexArray.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "BattleHex.h" + +VCMI_LIB_NAMESPACE_BEGIN + +/// Class representing an array of unique BattleHex objects +class DLL_LINKAGE BattleHexArray +{ +public: + static constexpr uint8_t totalSize = GameConstants::BFIELD_SIZE; + using StorageType = std::vector; + + using value_type = BattleHex; + using size_type = StorageType::size_type; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + using difference_type = typename StorageType::difference_type; + using iterator = typename StorageType::iterator; + using const_iterator = typename StorageType::const_iterator; + using reverse_iterator = typename StorageType::reverse_iterator; + using const_reverse_iterator = typename StorageType::const_reverse_iterator; + + using NeighbouringTiles = std::array; + using NeighbouringTilesCache = std::array; + + static const NeighbouringTilesCache neighbouringTilesCache; + + BattleHexArray() noexcept + { + internalStorage.reserve(totalSize); + } + + template >> + BattleHexArray(const Container & container) noexcept + : BattleHexArray() + { + for(auto value : container) + { + insert(value); + } + } + + void resize(size_type size) + { + clear(); + internalStorage.resize(size); + } + + BattleHexArray(std::initializer_list initList) noexcept; + + /// returns all valid neighbouring tiles + static BattleHexArray generateNeighbouringTiles(BattleHex hex) + { + BattleHexArray ret; + for(auto dir : BattleHex::hexagonalDirections()) + ret.checkAndPush(hex.cloneInDirection(dir, false)); + + return ret; + } + + /// returns all tiles, unavailable tiles will be set as invalid + /// order of returned tiles matches EDir enum + static BattleHexArray generateAllNeighbouringTiles(BattleHex hex) + { + BattleHexArray ret; + + ret.resize(6); + + for(auto dir : BattleHex::hexagonalDirections()) + ret.set(dir, hex.cloneInDirection(dir, false)); + + return ret; + } + + BattleHex getClosestTile(BattleSide side, BattleHex initialPos); + + void checkAndPush(BattleHex tile) + { + if(tile.isAvailable() && !contains(tile)) + { + presenceFlags[tile] = 1; + internalStorage.emplace_back(tile); + } + } + + void insert(BattleHex hex) noexcept + { + /*if(isNotValidForInsertion(hex)) + return;*/ + + if(contains(hex)) + return; + + presenceFlags[hex] = 1; + internalStorage.emplace_back(hex); + } + + void set(size_type index, BattleHex hex) + { + /*if(isNotValidForInsertion(hex)) + return;*/ + + if(contains(hex)) + return; + + presenceFlags[hex] = 1; + internalStorage[index] = hex; + } + + iterator BattleHexArray::insert(iterator pos, BattleHex hex) noexcept + { + /*if(isNotValidForInsertion(hex)) + return pos;*/ + + if(contains(hex)) + return pos; + + presenceFlags[hex] = 1; + return internalStorage.insert(pos, hex); + } + + void merge(const BattleHexArray & other) noexcept; + + void clear() noexcept; + inline void erase(size_type index) noexcept + { + assert(index < totalSize); + internalStorage[index] = BattleHex::INVALID; + presenceFlags[index] = 0; + } + void erase(iterator first, iterator last) noexcept; + inline void pop_back() noexcept + { + presenceFlags[internalStorage.back()] = 0; + internalStorage.pop_back(); + } + + inline std::vector toVector() const noexcept + { + return internalStorage; + } + + template + iterator findIf(Predicate predicate) noexcept + { + return std::find_if(begin(), end(), predicate); + } + + template + const_iterator findIf(Predicate predicate) const noexcept + { + return std::find_if(begin(), end(), predicate); + } + + template + BattleHexArray filterBy(Predicate predicate) const noexcept + { + BattleHexArray filtered; + for(auto hex : internalStorage) + { + if(predicate(hex)) + { + filtered.insert(hex); + } + } + return filtered; + } + + [[nodiscard]] inline bool BattleHexArray::contains(BattleHex hex) const noexcept + { + if(hex.isValid()) + return presenceFlags[hex]; + /* + if(!isTower(hex)) + logGlobal->warn("BattleHexArray::contains( %d ) - invalid BattleHex!", hex); + */ + + // return true for invalid hexes + return true; + } + + template + void serialize(Serializer & s) + { + s & internalStorage; + if(!internalStorage.empty() && presenceFlags[internalStorage.front()] == 0) + { + for(auto hex : internalStorage) + presenceFlags[hex] = 1; + } + } + + [[nodiscard]] inline const BattleHex & BattleHexArray::back() const noexcept + { + return internalStorage.back(); + } + + [[nodiscard]] inline const BattleHex & BattleHexArray::front() const noexcept + { + return internalStorage.front(); + } + + [[nodiscard]] inline const BattleHex & BattleHexArray::operator[](size_type index) const noexcept + { + return internalStorage[index]; + } + + [[nodiscard]] inline const BattleHex & BattleHexArray::at(size_type index) const + { + return internalStorage.at(index); + } + + [[nodiscard]] inline size_type size() const noexcept + { + return internalStorage.size(); + } + + [[nodiscard]] inline BattleHexArray::iterator BattleHexArray::begin() noexcept + { + return internalStorage.begin(); + } + + [[nodiscard]] inline BattleHexArray::const_iterator BattleHexArray::begin() const noexcept + { + return internalStorage.begin(); + } + + [[nodiscard]] inline bool BattleHexArray::empty() const noexcept + { + return internalStorage.empty(); + } + + [[nodiscard]] inline BattleHexArray::iterator BattleHexArray::end() noexcept + { + return internalStorage.end(); + } + + [[nodiscard]] inline BattleHexArray::const_iterator BattleHexArray::end() const noexcept + { + return internalStorage.end(); + } + + [[nodiscard]] inline BattleHexArray::reverse_iterator BattleHexArray::rbegin() noexcept + { + return reverse_iterator(end()); + } + + [[nodiscard]] inline BattleHexArray::const_reverse_iterator BattleHexArray::rbegin() const noexcept + { + return const_reverse_iterator(end()); + } + + [[nodiscard]] inline BattleHexArray::reverse_iterator BattleHexArray::rend() noexcept + { + return reverse_iterator(begin()); + } + + [[nodiscard]] inline BattleHexArray::const_reverse_iterator BattleHexArray::rend() const noexcept + { + return const_reverse_iterator(begin()); + } + +private: + StorageType internalStorage; + std::array presenceFlags = {}; + + [[nodiscard]] inline bool BattleHexArray::isNotValidForInsertion(BattleHex hex) const + { + if(isTower(hex)) + return true; + if(!hex.isValid()) + { + //logGlobal->warn("BattleHexArray::insert( %d ) - invalid BattleHex!", hex); + return true; + } + + return contains(hex) || internalStorage.size() >= totalSize; + } + + [[nodiscard]] inline bool isTower(BattleHex hex) const + { + return hex == BattleHex::CASTLE_CENTRAL_TOWER || hex == BattleHex::CASTLE_UPPER_TOWER || hex == BattleHex::CASTLE_BOTTOM_TOWER; + } +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 905d8a3a0..4a91d649e 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -207,7 +207,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const r.rand(1,8); //battle sound ID to play... can't do anything with it here int tilesToBlock = r.rand(5,12); - std::vector blockedTiles; + BattleHexArray blockedTiles; auto appropriateAbsoluteObstacle = [&](int id) { @@ -232,7 +232,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const currentBattle->obstacles.push_back(obstPtr); for(BattleHex blocked : obstPtr->getBlockedTiles()) - blockedTiles.push_back(blocked); + blockedTiles.insert(blocked); tilesToBlock -= Obstacle(obstPtr->ID).getInfo()->blockedTiles.size() / 2; } catch(RangeGenerator::ExhaustedPossibilities &) @@ -259,14 +259,14 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const return false; if(pos.getX() + obi.width > 15) return false; - if(vstd::contains(blockedTiles, pos)) + if(blockedTiles.contains(pos)) return false; for(BattleHex blocked : obi.getBlocked(pos)) { if(tileAccessibility[blocked] == EAccessibility::UNAVAILABLE) //for ship-to-ship battlefield - exclude hardcoded unavailable tiles return false; - if(vstd::contains(blockedTiles, blocked)) + if(blockedTiles.contains(blocked)) return false; int x = blocked.getX(); if(x <= 2 || x >= 14) @@ -285,7 +285,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const currentBattle->obstacles.push_back(obstPtr); for(BattleHex blocked : obstPtr->getBlockedTiles()) - blockedTiles.push_back(blocked); + blockedTiles.insert(blocked); tilesToBlock -= static_cast(obi.blockedTiles.size()); } } diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index a0bf78d14..3d09b5138 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -147,21 +147,21 @@ ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(const spells::Caster * return ESpellCastProblem::OK; } -std::pair< std::vector, int > CBattleInfoCallback::getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const +std::pair< BattleHexArray, int > CBattleInfoCallback::getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const { auto reachability = getReachability(stack); if(reachability.predecessors[dest] == -1) //cannot reach destination { - return std::make_pair(std::vector(), 0); + return std::make_pair(BattleHexArray(), 0); } //making the Path - std::vector path; + BattleHexArray path; BattleHex curElem = dest; while(curElem != start) { - path.push_back(curElem); + path.insert(curElem); curElem = reachability.predecessors[curElem]; } @@ -191,23 +191,21 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, return isWallPartAttackable(wallPart); }; // Count wall penalty requirement by shortest path, not by arbitrary line, to avoid various OH3 bugs - auto getShortestPath = [](BattleHex from, BattleHex dest) -> std::vector + auto getShortestPath = [](BattleHex from, BattleHex dest) -> BattleHexArray { //Out early if(from == dest) return {}; - std::vector ret; + BattleHexArray ret; auto next = from; //Not a real direction, only to indicate to which side we should search closest tile auto direction = from.getX() > dest.getX() ? BattleSide::DEFENDER : BattleSide::ATTACKER; while (next != dest) { - auto tiles = next.neighbouringTiles(); - std::set possibilities = {tiles.begin(), tiles.end()}; - next = BattleHex::getClosestTile(direction, dest, possibilities); - ret.push_back(next); + next = BattleHexArray::generateNeighbouringTiles(next).getClosestTile(direction, dest); + ret.insert(next); } assert(!ret.empty()); ret.pop_back(); //Remove destination hex @@ -318,9 +316,9 @@ PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * s return PossiblePlayerBattleAction(spellSelMode, spell->id); } -std::set CBattleInfoCallback::battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const +BattleHexArray CBattleInfoCallback::battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const { - std::set attackedHexes; + BattleHexArray attackedHexes; RETURN_IF_NOT_BATTLE(attackedHexes); AttackableTiles at = getPotentiallyAttackableHexes(attacker, destinationTile, attackerPos); @@ -347,7 +345,7 @@ const CStack* CBattleInfoCallback::battleGetStackByPos(BattleHex pos, bool onlyA { RETURN_IF_NOT_BATTLE(nullptr); for(const auto * s : battleGetAllStacks(true)) - if(vstd::contains(s->getHexes(), pos) && (!onlyAlive || s->alive())) + if(s->getHexes().contains(pos) && (!onlyAlive || s->alive())) return s; return nullptr; @@ -569,21 +567,21 @@ void CBattleInfoCallback::battleGetTurnOrder(std::vector & turns, battleGetTurnOrder(turns, maxUnits, maxTurns, actualTurn + 1, sideThatLastMoved); } -std::vector CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const +BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const { - RETURN_IF_NOT_BATTLE(std::vector()); + RETURN_IF_NOT_BATTLE(BattleHexArray()); if(!unit->getPosition().isValid()) //turrets - return std::vector(); + return BattleHexArray(); auto reachability = getReachability(unit); return battleGetAvailableHexes(reachability, unit, obtainMovementRange); } -std::vector CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const +BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const { - std::vector ret; + BattleHexArray ret; RETURN_IF_NOT_BATTLE(ret); if(!unit->getPosition().isValid()) //turrets @@ -612,28 +610,27 @@ std::vector CBattleInfoCallback::battleGetAvailableHexes(const Reacha continue; } - ret.emplace_back(i); + ret.insert(i); } return ret; } -std::vector CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, std::vector * attackable) const +BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, BattleHexArray * attackable) const { - std::vector ret = battleGetAvailableHexes(unit, obtainMovementRange); + BattleHexArray ret = battleGetAvailableHexes(unit, obtainMovementRange); if(ret.empty()) return ret; if(addOccupiable && unit->doubleWide()) { - std::vector occupiable; + BattleHexArray occupiable; - occupiable.reserve(ret.size()); for(auto hex : ret) - occupiable.push_back(unit->occupiedHex(hex)); + occupiable.insert(unit->occupiedHex(hex)); - vstd::concatenate(ret, occupiable); + ret.merge(occupiable); } @@ -643,36 +640,33 @@ std::vector CBattleInfoCallback::battleGetAvailableHexes(const battle { // Return true if given hex has at least one available neighbour. // Available hexes are already present in ret vector. - auto availableNeighbor = boost::find_if(ret, [=] (BattleHex availableHex) + auto availableneighbour = boost::find_if(ret, [=] (BattleHex availableHex) { return BattleHex::mutualPosition(hex, availableHex) >= 0; }); - return availableNeighbor != ret.end(); + return availableneighbour != ret.end(); }; for(const auto * otherSt : battleAliveUnits(otherSide(unit->unitSide()))) { if(!otherSt->isValidTarget(false)) continue; - std::vector occupied = otherSt->getHexes(); + BattleHexArray occupied = otherSt->getHexes(); if(battleCanShoot(unit, otherSt->getPosition())) { - attackable->insert(attackable->end(), occupied.begin(), occupied.end()); + attackable->merge(occupied); continue; } for(BattleHex he : occupied) { if(meleeAttackable(he)) - attackable->push_back(he); + attackable->insert(he); } } } - //adding occupiable likely adds duplicates to ret -> clean it up - boost::sort(ret); - ret.erase(boost::unique(ret).end(), ret.end()); return ret; } @@ -857,8 +851,8 @@ std::vector> CBattleInfoCallback::battl RETURN_IF_NOT_BATTLE(obstacles); for(auto & obs : battleGetAllObstacles()) { - if(vstd::contains(obs->getBlockedTiles(), tile) - || (!onlyBlocking && vstd::contains(obs->getAffectedTiles(), tile))) + if(obs->getBlockedTiles().contains(tile) + || (!onlyBlocking && obs->getAffectedTiles().contains(tile))) { obstacles.push_back(obs); } @@ -866,18 +860,18 @@ std::vector> CBattleInfoCallback::battl return obstacles; } -std::vector> CBattleInfoCallback::getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set & passed) const +std::vector> CBattleInfoCallback::getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const { auto affectedObstacles = std::vector>(); RETURN_IF_NOT_BATTLE(affectedObstacles); if(unit->alive()) { - if(!passed.count(unit->getPosition())) + if(!passed.contains(unit->getPosition())) affectedObstacles = battleGetAllObstaclesOnPos(unit->getPosition(), false); if(unit->doubleWide()) { BattleHex otherHex = unit->occupiedHex(); - if(otherHex.isValid() && !passed.count(otherHex)) + if(otherHex.isValid() && !passed.contains(otherHex)) for(auto & i : battleGetAllObstaclesOnPos(otherHex, false)) if(!vstd::contains(affectedObstacles, i)) affectedObstacles.push_back(i); @@ -891,7 +885,7 @@ std::vector> CBattleInfoCallback::getAl return affectedObstacles; } -bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set & passed) const +bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const BattleHexArray & passed) const { if(!unit.alive()) return false; @@ -970,7 +964,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const if(bFieldType != BattleField::NONE) { - std::vector impassableHexes = bFieldType.getInfo()->impassableHexes; + BattleHexArray impassableHexes = bFieldType.getInfo()->impassableHexes; for(auto hex : impassableHexes) ret[hex] = EAccessibility::UNAVAILABLE; @@ -1040,7 +1034,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility(const battle::Unit * sta return getAccessibility(battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide())); } -AccessibilityInfo CBattleInfoCallback::getAccessibility(const std::vector & accessibleHexes) const +AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray & accessibleHexes) const { auto ret = getAccessibility(); for(auto hex : accessibleHexes) @@ -1062,7 +1056,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi if(!params.startPosition.isValid()) //if got call for arrow turrets return ret; - const std::set obstacles = getStoppers(params.perspective); + const BattleHexArray obstacles = getStoppers(params.perspective); auto checkParams = params; checkParams.ignoreKnownAccessible = true; //Ignore starting hexes obstacles @@ -1087,7 +1081,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi const int costToNeighbour = ret.distances.at(curHex.hex) + 1; - for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex]) + for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[curHex.hex]) { if(neighbour.isValid()) { @@ -1120,17 +1114,17 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi bool CBattleInfoCallback::isInObstacle( BattleHex hex, - const std::set & obstacles, + const BattleHexArray & obstacleHexes, const ReachabilityInfo::Parameters & params) const { auto occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side); for(auto occupiedHex : occupiedHexes) { - if(params.ignoreKnownAccessible && vstd::contains(params.knownAccessible, occupiedHex)) + if(params.ignoreKnownAccessible && params.knownAccessible.contains(occupiedHex)) continue; - if(vstd::contains(obstacles, occupiedHex)) + if(obstacleHexes.contains(occupiedHex)) { if(occupiedHex == BattleHex::GATE_BRIDGE) { @@ -1145,9 +1139,9 @@ bool CBattleInfoCallback::isInObstacle( return false; } -std::set CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) const +BattleHexArray CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) const { - std::set ret; + BattleHexArray ret; RETURN_IF_NOT_BATTLE(ret); for(auto &oi : battleGetAllObstacles(whichSidePerspective)) @@ -1225,7 +1219,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS auto accessibility = getAccessibility(); - std::set occupyable; + BattleHexArray occupyable; for(int i = 0; i < accessibility.size(); i++) if(accessibility.accessible(i, twoHex, side)) occupyable.insert(i); @@ -1235,7 +1229,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS return BattleHex::INVALID; //all tiles are covered } - return BattleHex::getClosestTile(side, pos, occupyable); + return occupyable.getClosestTile(side, pos); } si8 CBattleInfoCallback::battleGetTacticDist() const @@ -1353,7 +1347,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( } if(attacker->hasBonusOfType(BonusType::THREE_HEADED_ATTACK)) { - std::vector hexes = attacker->getSurroundingHexes(attackerPos); + BattleHexArray hexes = attacker->getSurroundingHexes(attackerPos); for(BattleHex tile : hexes) { if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, attackOriginHex) > -1)) //adjacent both to attacker's head and attacked tile @@ -1366,12 +1360,12 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( } if(attacker->hasBonusOfType(BonusType::WIDE_BREATH)) { - std::vector hexes = destinationTile.neighbouringTiles(); - for(int i = 0; ihasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !vstd::contains(attackerPos.neighbouringTiles(), destinationTile)) + if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::generateNeighbouringTiles(attackerPos).contains(destinationTile)) { - std::vector targetHexes = destinationTile.neighbouringTiles(); - targetHexes.push_back(destinationTile); + auto targetHexes = BattleHexArray::generateNeighbouringTiles(destinationTile); + targetHexes.insert(destinationTile); boost::copy(targetHexes, vstd::set_inserter(at.hostileCreaturePositions)); } @@ -1480,9 +1474,9 @@ std::vector CBattleInfoCallback::getAttackedBattleUnits( for (BattleHex hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide())) { - if (vstd::contains(at.hostileCreaturePositions, hex)) + if (at.hostileCreaturePositions.contains(hex)) return true; - if (vstd::contains(at.friendlyCreaturePositions, hex)) + if (at.friendlyCreaturePositions.contains(hex)) return true; } return false; @@ -1671,15 +1665,15 @@ bool CBattleInfoCallback::isWallPartAttackable(EWallPart wallPart) const return false; } -std::vector CBattleInfoCallback::getAttackableBattleHexes() const +BattleHexArray CBattleInfoCallback::getAttackableBattleHexes() const { - std::vector attackableBattleHexes; + BattleHexArray attackableBattleHexes; RETURN_IF_NOT_BATTLE(attackableBattleHexes); for(const auto & wallPartPair : wallParts) { if(isWallPartAttackable(wallPartPair.second)) - attackableBattleHexes.emplace_back(wallPartPair.first); + attackableBattleHexes.insert(wallPartPair.first); } return attackableBattleHexes; diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index aedba1996..e7c11d5da 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -38,8 +38,8 @@ namespace spells struct DLL_LINKAGE AttackableTiles { - std::set hostileCreaturePositions; - std::set friendlyCreaturePositions; //for Dragon Breath + BattleHexArray hostileCreaturePositions; + BattleHexArray friendlyCreaturePositions; //for Dragon Breath template void serialize(Handler &h) { h & hostileCreaturePositions; @@ -59,9 +59,9 @@ public: std::optional battleIsFinished() const override; //return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const override; - std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set & passed) const override; + std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const override; //Handle obstacle damage here, requires SpellCastEnvironment - bool handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set & passed = {}) const; + bool handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const BattleHexArray & passed = {}) const; const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const; @@ -75,20 +75,20 @@ public: void battleGetTurnOrder(std::vector & out, const size_t maxUnits, const int maxTurns, const int turn = 0, BattleSide lastMoved = BattleSide::NONE) const; ///returns reachable hexes (valid movement destinations), DOES contain stack current position - std::vector battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, std::vector * attackable) const; + BattleHexArray battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange, bool addOccupiable, BattleHexArray * attackable) const; ///returns reachable hexes (valid movement destinations), DOES contain stack current position (lite version) - std::vector battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const; + BattleHexArray battleGetAvailableHexes(const battle::Unit * unit, bool obtainMovementRange) const; - std::vector battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const; + BattleHexArray battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool obtainMovementRange) const; int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const; - std::set battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; + BattleHexArray battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const; bool isHexWithinSpecifiedRange(BattleHex attackerPosition, BattleHex targetPosition, unsigned int range) const; - std::pair< std::vector, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const; + std::pair< BattleHexArray, int > getPath(BattleHex start, BattleHex dest, const battle::Unit * stack) const; bool battleCanTargetEmptyHex(const battle::Unit * attacker) const; //determines of stack with given ID can target empty hex to attack - currently used only for SPELL_LIKE_ATTACK shooting bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination @@ -116,7 +116,7 @@ public: EWallPart battleHexToWallPart(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found bool isWallPartPotentiallyAttackable(EWallPart wallPart) const; // returns true if the wall part is potentially attackable (independent of wall state), false if not bool isWallPartAttackable(EWallPart wallPart) const; // returns true if the wall part is actually attackable, false if not - std::vector getAttackableBattleHexes() const; + BattleHexArray getAttackableBattleHexes() const; si8 battleMinSpellLevel(BattleSide side) const; //calculates maximum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned si8 battleMaxSpellLevel(BattleSide side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned @@ -162,15 +162,15 @@ public: ReachabilityInfo getReachability(const ReachabilityInfo::Parameters & params) const; AccessibilityInfo getAccessibility() const; AccessibilityInfo getAccessibility(const battle::Unit * stack) const; //Hexes occupied by stack will be marked as accessible. - AccessibilityInfo getAccessibility(const std::vector & accessibleHexes) const; //given hexes will be marked as accessible + AccessibilityInfo getAccessibility(const BattleHexArray & accessibleHexes) const; //given hexes will be marked as accessible std::pair getNearestStack(const battle::Unit * closest) const; BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack protected: ReachabilityInfo getFlyingReachability(const ReachabilityInfo::Parameters & params) const; ReachabilityInfo makeBFS(const AccessibilityInfo & accessibility, const ReachabilityInfo::Parameters & params) const; - bool isInObstacle(BattleHex hex, const std::set & obstacles, const ReachabilityInfo::Parameters & params) const; - std::set getStoppers(BattleSide whichSidePerspective) const; //get hexes with stopping obstacles (quicksands) + bool isInObstacle(BattleHex hex, const BattleHexArray & obstacles, const ReachabilityInfo::Parameters & params) const; + BattleHexArray getStoppers(BattleSide whichSidePerspective) const; //get hexes with stopping obstacles (quicksands) }; -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/CObstacleInstance.cpp b/lib/battle/CObstacleInstance.cpp index 6bb5c65f3..c0d45def2 100644 --- a/lib/battle/CObstacleInstance.cpp +++ b/lib/battle/CObstacleInstance.cpp @@ -24,21 +24,21 @@ const ObstacleInfo & CObstacleInstance::getInfo() const return *Obstacle(ID).getInfo(); } -std::vector CObstacleInstance::getBlockedTiles() const +BattleHexArray CObstacleInstance::getBlockedTiles() const { if(blocksTiles()) return getAffectedTiles(); - return std::vector(); + return BattleHexArray(); } -std::vector CObstacleInstance::getStoppingTile() const +BattleHexArray CObstacleInstance::getStoppingTile() const { if(stopsMovement()) return getAffectedTiles(); - return std::vector(); + return BattleHexArray(); } -std::vector CObstacleInstance::getAffectedTiles() const +BattleHexArray CObstacleInstance::getAffectedTiles() const { switch(obstacleType) { @@ -47,7 +47,7 @@ std::vector CObstacleInstance::getAffectedTiles() const return getInfo().getBlocked(pos); default: assert(0); - return std::vector(); + return BattleHexArray(); } } @@ -215,12 +215,16 @@ void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler) JsonArraySerializer customSizeJson = handler.enterArray("customSize"); customSizeJson.syncSize(customSize, JsonNode::JsonType::DATA_INTEGER); + BattleHex hex; for(size_t index = 0; index < customSizeJson.size(); index++) - customSizeJson.serializeInt(index, customSize.at(index)); + { + customSizeJson.serializeInt(index, hex); + customSize.set(index, hex); + } } } -std::vector SpellCreatedObstacle::getAffectedTiles() const +BattleHexArray SpellCreatedObstacle::getAffectedTiles() const { return customSize; } @@ -258,4 +262,4 @@ int SpellCreatedObstacle::getAnimationYOffset(int imageHeight) const return offset; } -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/CObstacleInstance.h b/lib/battle/CObstacleInstance.h index 3ce98a202..dd9a924cf 100644 --- a/lib/battle/CObstacleInstance.h +++ b/lib/battle/CObstacleInstance.h @@ -8,7 +8,7 @@ * */ #pragma once -#include "BattleHex.h" +#include "BattleHexArray.h" #include "../constants/EntityIdentifiers.h" #include "../filesystem/ResourcePath.h" @@ -39,8 +39,8 @@ struct DLL_LINKAGE CObstacleInstance : public Serializeable const ObstacleInfo &getInfo() const; //allowed only when not generated by spell (usual or absolute) - std::vector getBlockedTiles() const; - std::vector getStoppingTile() const; //hexes that will stop stack move + BattleHexArray getBlockedTiles() const; + BattleHexArray getStoppingTile() const; //hexes that will stop stack move //The two functions below describe how the obstacle affects affected tiles //additional effects (like hurting stack or disappearing) are hardcoded for appropriate obstacleTypes @@ -49,7 +49,7 @@ struct DLL_LINKAGE CObstacleInstance : public Serializeable virtual bool triggersEffects() const; virtual SpellID getTrigger() const; - virtual std::vector getAffectedTiles() const; + virtual BattleHexArray getAffectedTiles() const; virtual bool visibleForSide(BattleSide side, bool hasNativeStack) const; //0 attacker virtual void battleTurnPassed(){}; @@ -97,11 +97,11 @@ struct DLL_LINKAGE SpellCreatedObstacle : CObstacleInstance int animationYOffset; - std::vector customSize; + BattleHexArray customSize; SpellCreatedObstacle(); - std::vector getAffectedTiles() const override; + BattleHexArray getAffectedTiles() const override; bool visibleForSide(BattleSide side, bool hasNativeStack) const override; bool blocksTiles() const override; diff --git a/lib/battle/Destination.h b/lib/battle/Destination.h index 2dc0a450c..4d321589d 100644 --- a/lib/battle/Destination.h +++ b/lib/battle/Destination.h @@ -1,43 +1,43 @@ -/* - * Destination.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "BattleHex.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace battle -{ - -class Unit; - -class DLL_LINKAGE Destination -{ -public: - Destination(); - ~Destination() = default; - explicit Destination(const Unit * destination); - explicit Destination(const BattleHex & destination); - explicit Destination(const Unit * destination, const BattleHex & exactHex); - - Destination(const Destination & other) = default; - - Destination & operator=(const Destination & other) = default; - - const Unit * unitValue; - BattleHex hexValue; -}; - -using Target = std::vector; - -} - -VCMI_LIB_NAMESPACE_END +/* + * Destination.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "BattleHexArray.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace battle +{ + +class Unit; + +class DLL_LINKAGE Destination +{ +public: + Destination(); + ~Destination() = default; + explicit Destination(const Unit * destination); + explicit Destination(const BattleHex & destination); + explicit Destination(const Unit * destination, const BattleHex & exactHex); + + Destination(const Destination & other) = default; + + Destination & operator=(const Destination & other) = default; + + const Unit * unitValue; + BattleHex hexValue; +}; + +using Target = std::vector; + +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index ca43cb767..c858e6e9f 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -1,89 +1,89 @@ -/* - * IBattleInfoCallback.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "GameConstants.h" -#include "BattleHex.h" - -#include - -#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false) - -VCMI_LIB_NAMESPACE_BEGIN - -struct CObstacleInstance; -class BattleField; -class IBattleInfo; - -namespace battle -{ - class IUnitInfo; - class Unit; - using Units = std::vector; - using UnitFilter = std::function; -} - -struct DamageRange -{ - int64_t min = 0; - int64_t max = 0; -}; - -struct DamageEstimation -{ - DamageRange damage; - DamageRange kills; -}; - -#if SCRIPTING_ENABLED -namespace scripting -{ - class Pool; -} -#endif - -class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider -{ -public: -#if SCRIPTING_ENABLED - virtual scripting::Pool * getContextPool() const = 0; -#endif - virtual ~IBattleInfoCallback() = default; - - virtual const IBattleInfo * getBattle() const = 0; - virtual std::optional getPlayerID() const = 0; - - virtual TerrainId battleTerrainType() const = 0; - virtual BattleField battleGetBattlefieldType() const = 0; - - ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw - virtual std::optional battleIsFinished() const = 0; - - virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase - virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?) - - virtual uint32_t battleNextUnitId() const = 0; - - virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0; - - virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0; - virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0; - - virtual const battle::Unit * battleActiveUnit() const = 0; - - //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) - virtual std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0; - virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set & passed) const = 0; -}; - - - -VCMI_LIB_NAMESPACE_END +/* + * IBattleInfoCallback.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "GameConstants.h" +#include "BattleHexArray.h" + +#include + +#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false) + +VCMI_LIB_NAMESPACE_BEGIN + +struct CObstacleInstance; +class BattleField; +class IBattleInfo; + +namespace battle +{ + class IUnitInfo; + class Unit; + using Units = std::vector; + using UnitFilter = std::function; +} + +struct DamageRange +{ + int64_t min = 0; + int64_t max = 0; +}; + +struct DamageEstimation +{ + DamageRange damage; + DamageRange kills; +}; + +#if SCRIPTING_ENABLED +namespace scripting +{ + class Pool; +} +#endif + +class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider +{ +public: +#if SCRIPTING_ENABLED + virtual scripting::Pool * getContextPool() const = 0; +#endif + virtual ~IBattleInfoCallback() = default; + + virtual const IBattleInfo * getBattle() const = 0; + virtual std::optional getPlayerID() const = 0; + + virtual TerrainId battleTerrainType() const = 0; + virtual BattleField battleGetBattlefieldType() const = 0; + + ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw + virtual std::optional battleIsFinished() const = 0; + + virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase + virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?) + + virtual uint32_t battleNextUnitId() const = 0; + + virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0; + + virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0; + virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0; + + virtual const battle::Unit * battleActiveUnit() const = 0; + + //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) + virtual std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0; + virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const = 0; +}; + + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index c0165b7af..a96dc3fd6 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -1,91 +1,89 @@ -/* - * ReachabilityInfo.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "ReachabilityInfo.h" -#include "Unit.h" - -VCMI_LIB_NAMESPACE_BEGIN - -ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition): - perspective(static_cast(Stack->unitSide())), - startPosition(StartPosition), - doubleWide(Stack->doubleWide()), - side(Stack->unitSide()), - flying(Stack->hasBonusOfType(BonusType::FLYING)) -{ - knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); -} - -ReachabilityInfo::ReachabilityInfo() -{ - distances.fill(INFINITE_DIST); - predecessors.fill(BattleHex::INVALID); -} - -bool ReachabilityInfo::isReachable(BattleHex hex) const -{ - return distances[hex] < INFINITE_DIST; -} - -uint32_t ReachabilityInfo::distToNearestNeighbour( - const std::vector & targetHexes, - BattleHex * chosenHex) const -{ - uint32_t ret = 1000000; - - for(auto targetHex : targetHexes) - { - for(auto & n : targetHex.neighbouringTiles()) - { - if(distances[n] < ret) - { - ret = distances[n]; - if(chosenHex) - *chosenHex = n; - } - } - } - - return ret; -} - -uint32_t ReachabilityInfo::distToNearestNeighbour( - const battle::Unit * attacker, - const battle::Unit * defender, - BattleHex * chosenHex) const -{ - auto attackableHexes = defender->getHexes(); - - if(attacker->doubleWide()) - { - if(defender->doubleWide()) - { - // It can be back to back attack o==o or head to head =oo=. - // In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles - vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); - } - else - { - vstd::concatenate(attackableHexes, battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); - } - } - - vstd::removeDuplicates(attackableHexes); - - vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool - { - return h.getY() != defender->getPosition().getY() || !h.isAvailable(); - }); - - return distToNearestNeighbour(attackableHexes, chosenHex); -} - -VCMI_LIB_NAMESPACE_END +/* + * ReachabilityInfo.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "ReachabilityInfo.h" +#include "Unit.h" + +VCMI_LIB_NAMESPACE_BEGIN + +ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition): + perspective(static_cast(Stack->unitSide())), + startPosition(StartPosition), + doubleWide(Stack->doubleWide()), + side(Stack->unitSide()), + flying(Stack->hasBonusOfType(BonusType::FLYING)) +{ + knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); +} + +ReachabilityInfo::ReachabilityInfo() +{ + distances.fill(INFINITE_DIST); + predecessors.fill(BattleHex::INVALID); +} + +bool ReachabilityInfo::isReachable(BattleHex hex) const +{ + return distances[hex] < INFINITE_DIST; +} + +uint32_t ReachabilityInfo::distToNearestNeighbour( + const BattleHexArray & targetHexes, + BattleHex * chosenHex) const +{ + uint32_t ret = 1000000; + + for(auto targetHex : targetHexes) + { + for(auto & n : BattleHexArray::generateNeighbouringTiles(targetHex)) + { + if(distances[n] < ret) + { + ret = distances[n]; + if(chosenHex) + *chosenHex = n; + } + } + } + + return ret; +} + +uint32_t ReachabilityInfo::distToNearestNeighbour( + const battle::Unit * attacker, + const battle::Unit * defender, + BattleHex * chosenHex) const +{ + auto attackableHexes = defender->getHexes(); + + if(attacker->doubleWide()) + { + if(defender->doubleWide()) + { + // It can be back to back attack o==o or head to head =oo=. + // In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles + attackableHexes.merge(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); + } + else + { + attackableHexes.merge(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); + } + } + + vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool + { + return h.getY() != defender->getPosition().getY() || !h.isAvailable(); + }); + + return distToNearestNeighbour(attackableHexes, chosenHex); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/ReachabilityInfo.h b/lib/battle/ReachabilityInfo.h index f0c5ed948..ddd702d9f 100644 --- a/lib/battle/ReachabilityInfo.h +++ b/lib/battle/ReachabilityInfo.h @@ -8,12 +8,14 @@ * */ #pragma once -#include "BattleHex.h" +#include "BattleHexArray.h" #include "CBattleInfoEssentials.h" #include "AccessibilityInfo.h" VCMI_LIB_NAMESPACE_BEGIN +class BattleHexArray; + // Reachability info is result of BFS calculation. It's dependent on stack (it's owner, whether it's flying), // startPosition and perspective. struct DLL_LINKAGE ReachabilityInfo @@ -30,7 +32,7 @@ struct DLL_LINKAGE ReachabilityInfo bool flying = false; bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward - std::vector knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself) + BattleHexArray knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself) std::map destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex BattleHex startPosition; //assumed position of stack @@ -50,7 +52,7 @@ struct DLL_LINKAGE ReachabilityInfo bool isReachable(BattleHex hex) const; uint32_t distToNearestNeighbour( - const std::vector & targetHexes, + const BattleHexArray & targetHexes, BattleHex * chosenHex = nullptr) const; uint32_t distToNearestNeighbour( diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index 51bf21d77..2e74a9133 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -1,242 +1,240 @@ -/* - * Unit.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include "Unit.h" - -#include "../VCMI_Lib.h" -#include "../texts/CGeneralTextHandler.h" - -#include "../serializer/JsonDeserializer.h" -#include "../serializer/JsonSerializer.h" - -#include -#include - -VCMI_LIB_NAMESPACE_BEGIN - -namespace battle -{ - -///Unit -Unit::~Unit() = default; - -bool Unit::isDead() const -{ - return !alive() && !isGhost(); -} - -bool Unit::isTurret() const -{ - return creatureIndex() == CreatureID::ARROW_TOWERS; -} - -std::string Unit::getDescription() const -{ - boost::format fmt("Unit %d of side %d"); - fmt % unitId() % static_cast(unitSide()); - return fmt.str(); -} - -//TODO: deduplicate these functions -const IBonusBearer* Unit::getBonusBearer() const -{ - return this; -} - -std::vector Unit::getSurroundingHexes(BattleHex assumedPosition) const -{ - BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position - - return getSurroundingHexes(hex, doubleWide(), unitSide()); -} - -std::vector Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) -{ - std::vector hexes; - if(twoHex) - { - const BattleHex otherHex = occupiedHex(position, twoHex, side); - - if(side == BattleSide::ATTACKER) - { - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - BattleHex::checkAndPush(position.cloneInDirection(dir, false), hexes); - - BattleHex::checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), hexes); - BattleHex::checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false), hexes); - BattleHex::checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false), hexes); - } - else - { - BattleHex::checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false), hexes); - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - BattleHex::checkAndPush(otherHex.cloneInDirection(dir, false), hexes); - - BattleHex::checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), hexes); - BattleHex::checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false), hexes); - } - return hexes; - } - else - { - return position.neighbouringTiles(); - } -} - -std::vector Unit::getAttackableHexes(const Unit * attacker) const -{ - auto defenderHexes = battle::Unit::getHexes( - getPosition(), - doubleWide(), - unitSide()); - - std::vector targetableHexes; - - for(auto defenderHex : defenderHexes) - { - auto hexes = battle::Unit::getHexes( - defenderHex, - attacker->doubleWide(), - unitSide()); - - if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1) - hexes.pop_back(); - - for(auto hex : hexes) - vstd::concatenate(targetableHexes, hex.neighbouringTiles()); - } - - vstd::removeDuplicates(targetableHexes); - - return targetableHexes; -} - -bool Unit::coversPos(BattleHex pos) const -{ - return getPosition() == pos || (doubleWide() && (occupiedHex() == pos)); -} - -std::vector Unit::getHexes() const -{ - return getHexes(getPosition(), doubleWide(), unitSide()); -} - -std::vector Unit::getHexes(BattleHex assumedPos) const -{ - return getHexes(assumedPos, doubleWide(), unitSide()); -} - -std::vector Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) -{ - std::vector hexes; - hexes.push_back(assumedPos); - - if(twoHex) - hexes.push_back(occupiedHex(assumedPos, twoHex, side)); - - return hexes; -} - -BattleHex Unit::occupiedHex() const -{ - return occupiedHex(getPosition(), doubleWide(), unitSide()); -} - -BattleHex Unit::occupiedHex(BattleHex assumedPos) const -{ - return occupiedHex(assumedPos, doubleWide(), unitSide()); -} - -BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side) -{ - if(twoHex) - { - if(side == BattleSide::ATTACKER) - return assumedPos - 1; - else - return assumedPos + 1; - } - else - { - return BattleHex::INVALID; - } -} - -void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const -{ - if(boost::logic::indeterminate(plural)) - serial = VLC->generaltexth->pluralText(serial, getCount()); - else if(plural) - serial = VLC->generaltexth->pluralText(serial, 2); - else - serial = VLC->generaltexth->pluralText(serial, 1); - - text.appendLocalString(type, serial); -} - -void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const -{ - if(boost::logic::indeterminate(plural)) - text.replaceName(creatureId(), getCount()); - else if(plural) - text.replaceNamePlural(creatureIndex()); - else - text.replaceNameSingular(creatureIndex()); -} - -std::string Unit::formatGeneralMessage(const int32_t baseTextId) const -{ - const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount()); - - MetaString text; - text.appendLocalString(EMetaText::GENERAL_TXT, textId); - text.replaceName(creatureId(), getCount()); - - return text.toString(); -} - -int Unit::getRawSurrenderCost() const -{ - //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines - if(unitSlot().validSlot()) - return creatureCost() * getCount(); - else - return 0; -} - -///UnitInfo -void UnitInfo::serializeJson(JsonSerializeFormat & handler) -{ - handler.serializeInt("count", count); - handler.serializeId("type", type, CreatureID(CreatureID::NONE)); - handler.serializeInt("side", side); - handler.serializeInt("position", position); - handler.serializeBool("summoned", summoned); -} - -void UnitInfo::save(JsonNode & data) -{ - data.clear(); - JsonSerializer ser(nullptr, data); - ser.serializeStruct("newUnitInfo", *this); -} - -void UnitInfo::load(uint32_t id_, const JsonNode & data) -{ - id = id_; - JsonDeserializer deser(nullptr, data); - deser.serializeStruct("newUnitInfo", *this); -} - -} - -VCMI_LIB_NAMESPACE_END +/* + * Unit.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "Unit.h" + +#include "../VCMI_Lib.h" +#include "../texts/CGeneralTextHandler.h" + +#include "../serializer/JsonDeserializer.h" +#include "../serializer/JsonSerializer.h" + +#include +#include + +VCMI_LIB_NAMESPACE_BEGIN + +namespace battle +{ + +///Unit +Unit::~Unit() = default; + +bool Unit::isDead() const +{ + return !alive() && !isGhost(); +} + +bool Unit::isTurret() const +{ + return creatureIndex() == CreatureID::ARROW_TOWERS; +} + +std::string Unit::getDescription() const +{ + boost::format fmt("Unit %d of side %d"); + fmt % unitId() % static_cast(unitSide()); + return fmt.str(); +} + +//TODO: deduplicate these functions +const IBonusBearer* Unit::getBonusBearer() const +{ + return this; +} + +BattleHexArray Unit::getSurroundingHexes(BattleHex assumedPosition) const +{ + BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position + + return getSurroundingHexes(hex, doubleWide(), unitSide()); +} + +BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) +{ + BattleHexArray hexes; + if(twoHex) + { + const BattleHex otherHex = occupiedHex(position, twoHex, side); + + if(side == BattleSide::ATTACKER) + { + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(position.cloneInDirection(dir, false)); + + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + } + else + { + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); + + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false)); + } + return hexes; + } + else + { + return BattleHexArray::generateNeighbouringTiles(position); + } +} + +BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const +{ + auto defenderHexes = battle::Unit::getHexes( + getPosition(), + doubleWide(), + unitSide()); + + BattleHexArray targetableHexes; + + for(auto defenderHex : defenderHexes) + { + auto hexes = battle::Unit::getHexes( + defenderHex, + attacker->doubleWide(), + unitSide()); + + if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1) + hexes.pop_back(); + + for(auto hex : hexes) + targetableHexes.merge(BattleHexArray::generateNeighbouringTiles(hex)); + } + + return targetableHexes; +} + +bool Unit::coversPos(BattleHex pos) const +{ + return getPosition() == pos || (doubleWide() && (occupiedHex() == pos)); +} + +BattleHexArray Unit::getHexes() const +{ + return getHexes(getPosition(), doubleWide(), unitSide()); +} + +BattleHexArray Unit::getHexes(BattleHex assumedPos) const +{ + return getHexes(assumedPos, doubleWide(), unitSide()); +} + +BattleHexArray Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) +{ + BattleHexArray hexes; + hexes.insert(assumedPos); + + if(twoHex) + hexes.insert(occupiedHex(assumedPos, twoHex, side)); + + return hexes; +} + +BattleHex Unit::occupiedHex() const +{ + return occupiedHex(getPosition(), doubleWide(), unitSide()); +} + +BattleHex Unit::occupiedHex(BattleHex assumedPos) const +{ + return occupiedHex(assumedPos, doubleWide(), unitSide()); +} + +BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side) +{ + if(twoHex) + { + if(side == BattleSide::ATTACKER) + return assumedPos - 1; + else + return assumedPos + 1; + } + else + { + return BattleHex::INVALID; + } +} + +void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const +{ + if(boost::logic::indeterminate(plural)) + serial = VLC->generaltexth->pluralText(serial, getCount()); + else if(plural) + serial = VLC->generaltexth->pluralText(serial, 2); + else + serial = VLC->generaltexth->pluralText(serial, 1); + + text.appendLocalString(type, serial); +} + +void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const +{ + if(boost::logic::indeterminate(plural)) + text.replaceName(creatureId(), getCount()); + else if(plural) + text.replaceNamePlural(creatureIndex()); + else + text.replaceNameSingular(creatureIndex()); +} + +std::string Unit::formatGeneralMessage(const int32_t baseTextId) const +{ + const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount()); + + MetaString text; + text.appendLocalString(EMetaText::GENERAL_TXT, textId); + text.replaceName(creatureId(), getCount()); + + return text.toString(); +} + +int Unit::getRawSurrenderCost() const +{ + //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines + if(unitSlot().validSlot()) + return creatureCost() * getCount(); + else + return 0; +} + +///UnitInfo +void UnitInfo::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeInt("count", count); + handler.serializeId("type", type, CreatureID(CreatureID::NONE)); + handler.serializeInt("side", side); + handler.serializeInt("position", position); + handler.serializeBool("summoned", summoned); +} + +void UnitInfo::save(JsonNode & data) +{ + data.clear(); + JsonSerializer ser(nullptr, data); + ser.serializeStruct("newUnitInfo", *this); +} + +void UnitInfo::load(uint32_t id_, const JsonNode & data) +{ + id = id_; + JsonDeserializer deser(nullptr, data); + deser.serializeStruct("newUnitInfo", *this); +} + +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/Unit.h b/lib/battle/Unit.h index 574a111be..e900eaae3 100644 --- a/lib/battle/Unit.h +++ b/lib/battle/Unit.h @@ -17,7 +17,7 @@ #include "../bonuses/IBonusBearer.h" #include "IUnitInfo.h" -#include "BattleHex.h" +#include "BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN @@ -84,14 +84,11 @@ public: bool isTurret() const; virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect) - virtual bool isHypnotized() const = 0; - virtual bool isClone() const = 0; virtual bool hasClone() const = 0; virtual bool canCast() const = 0; virtual bool isCaster() const = 0; - virtual bool canShootBlocked() const = 0; virtual bool canShoot() const = 0; virtual bool isShooter() const = 0; @@ -115,6 +112,8 @@ public: virtual BattleHex getPosition() const = 0; virtual void setPosition(BattleHex hex) = 0; + virtual int32_t getInitiative(int turn = 0) const = 0; + virtual bool canMove(int turn = 0) const = 0; //if stack can move virtual bool defended(int turn = 0) const = 0; virtual bool moved(int turn = 0) const = 0; //if stack was already moved this turn @@ -128,15 +127,15 @@ public: virtual std::string getDescription() const; - std::vector getSurroundingHexes(BattleHex assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size - std::vector getAttackableHexes(const Unit * attacker) const; - static std::vector getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side); + BattleHexArray getSurroundingHexes(BattleHex assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size + BattleHexArray getAttackableHexes(const Unit * attacker) const; + static BattleHexArray getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side); bool coversPos(BattleHex position) const; //checks also if unit is double-wide - std::vector getHexes() const; //up to two occupied hexes, starting from front - std::vector getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front - static std::vector getHexes(BattleHex assumedPos, bool twoHex, BattleSide side); + BattleHexArray getHexes() const; //up to two occupied hexes, starting from front + BattleHexArray getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front + static BattleHexArray getHexes(BattleHex assumedPos, bool twoHex, BattleSide side); BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1 BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1 diff --git a/lib/bonuses/BonusEnum.h b/lib/bonuses/BonusEnum.h index 1ef515e1f..f2daccac0 100644 --- a/lib/bonuses/BonusEnum.h +++ b/lib/bonuses/BonusEnum.h @@ -142,7 +142,7 @@ class JsonNode; BONUS_NAME(WIDE_BREATH) /* initial desigh: dragon breath affecting multiple nearby hexes */\ BONUS_NAME(FIRST_STRIKE) /* first counterattack, then attack if possible */\ BONUS_NAME(SYNERGY_TARGET) /* dummy skill for alternative upgrades mod */\ - BONUS_NAME(SHOOTS_ALL_ADJACENT) /* H4 Cyclops-like shoot (attacks all hexes neighboring with target) without spell-like mechanics */\ + BONUS_NAME(SHOOTS_ALL_ADJACENT) /* H4 Cyclops-like shoot (attacks all hexes neighbouring with target) without spell-like mechanics */\ BONUS_NAME(BLOCK_MAGIC_BELOW) /*blocks casting spells of the level < value */ \ BONUS_NAME(DESTRUCTION) /*kills extra units after hit, subtype = 0 - kill percentage of units, 1 - kill amount, val = chance in percent to trigger, additional info - amount/percentage to kill*/ \ BONUS_NAME(SPECIAL_CRYSTAL_GENERATION) /*crystal dragon crystal generation*/ \ diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index e5aa2fae3..f84b2de48 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -226,7 +226,7 @@ ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &contex return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; } -UnitOnHexLimiter::UnitOnHexLimiter(const std::set & applicableHexes): +UnitOnHexLimiter::UnitOnHexLimiter(const BattleHexArray & applicableHexes): applicableHexes(applicableHexes) { } diff --git a/lib/bonuses/Limiters.h b/lib/bonuses/Limiters.h index 9ad0e56d2..b525a153a 100644 --- a/lib/bonuses/Limiters.h +++ b/lib/bonuses/Limiters.h @@ -263,9 +263,9 @@ public: class DLL_LINKAGE UnitOnHexLimiter : public ILimiter //works only on selected hexes { public: - std::set applicableHexes; + BattleHexArray applicableHexes; - UnitOnHexLimiter(const std::set & applicableHexes = {}); + UnitOnHexLimiter(const BattleHexArray & applicableHexes = {}); EDecision limit(const BonusLimitationContext &context) const override; JsonNode toJsonNode() const override; diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 8d389cf38..ba7dbf72f 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -278,7 +278,7 @@ CGHeroInstance * CMap::getHero(HeroTypeID heroID) bool CMap::isCoastalTile(const int3 & pos) const { - //todo: refactoring: extract neighbor tile iterator and use it in GameState + //todo: refactoring: extract neighbour tile iterator and use it in GameState static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; diff --git a/lib/networkPacks/PacksForClientBattle.h b/lib/networkPacks/PacksForClientBattle.h index fcaf85457..eacd574ef 100644 --- a/lib/networkPacks/PacksForClientBattle.h +++ b/lib/networkPacks/PacksForClientBattle.h @@ -22,6 +22,7 @@ class CGHeroInstance; class CArmedInstance; class IBattleState; class BattleInfo; +class BattleHexArray; struct DLL_LINKAGE BattleStart : public CPackForClient { @@ -170,10 +171,10 @@ struct DLL_LINKAGE BattleStackMoved : public CPackForClient { BattleID battleID = BattleID::NONE; ui32 stack = 0; - std::vector tilesToMove; + BattleHexArray tilesToMove; int distance = 0; bool teleporting = false; - + void applyGs(CGameState * gs) override; void applyBattle(IBattleState * battleState); diff --git a/lib/pathfinder/CGPathNode.h b/lib/pathfinder/CGPathNode.h index 2598633cc..fd9f80d3c 100644 --- a/lib/pathfinder/CGPathNode.h +++ b/lib/pathfinder/CGPathNode.h @@ -1,243 +1,243 @@ -/* - * CGPathNode.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../GameConstants.h" -#include "../int3.h" - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -class CGHeroInstance; -class CGObjectInstance; -class CGameState; -class CPathfinderHelper; -struct TerrainTile; - -template -struct DLL_LINKAGE NodeComparer -{ - STRONG_INLINE - bool operator()(const N * lhs, const N * rhs) const - { - return lhs->getCost() > rhs->getCost(); - } -}; - -enum class EPathAccessibility : ui8 -{ - NOT_SET, - ACCESSIBLE, //tile can be entered and passed - VISITABLE, //tile can be entered as the last tile in path - GUARDED, //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any) - BLOCKVIS, //visitable from neighboring tile but not passable - FLYABLE, //can only be accessed in air layer - BLOCKED //tile can be neither entered nor visited -}; - -enum class EPathNodeAction : ui8 -{ - UNKNOWN, - EMBARK, - DISEMBARK, - NORMAL, - BATTLE, - VISIT, - BLOCKING_VISIT, - TELEPORT_NORMAL, - TELEPORT_BLOCKING_VISIT, - TELEPORT_BATTLE -}; - -struct DLL_LINKAGE CGPathNode -{ - using TFibHeap = boost::heap::fibonacci_heap>>; - using ELayer = EPathfindingLayer; - - TFibHeap::handle_type pqHandle; - TFibHeap * pq; - CGPathNode * theNodeBefore; - - int3 coord; //coordinates - ELayer layer; - - float cost; //total cost of the path to this tile measured in turns with fractions - int moveRemains; //remaining movement points after hero reaches the tile - ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn - EPathAccessibility accessible; - EPathNodeAction action; - bool locked; - - CGPathNode() - : coord(-1), - layer(ELayer::WRONG), - pqHandle(nullptr) - { - reset(); - } - - STRONG_INLINE - void reset() - { - locked = false; - accessible = EPathAccessibility::NOT_SET; - moveRemains = 0; - cost = std::numeric_limits::max(); - turns = 255; - theNodeBefore = nullptr; - pq = nullptr; - action = EPathNodeAction::UNKNOWN; - } - - STRONG_INLINE - bool inPQ() const - { - return pq != nullptr; - } - - STRONG_INLINE - float getCost() const - { - return cost; - } - - STRONG_INLINE - void setCost(float value) - { - if(vstd::isAlmostEqual(value, cost)) - return; - - bool getUpNode = value < cost; - cost = value; - // If the node is in the heap, update the heap. - if(inPQ()) - { - if(getUpNode) - { - pq->increase(this->pqHandle); - } - else - { - pq->decrease(this->pqHandle); - } - } - } - - STRONG_INLINE - void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible) - { - if(layer == ELayer::WRONG) - { - coord = Coord; - layer = Layer; - } - else - { - reset(); - } - - accessible = Accessible; - } - - STRONG_INLINE - bool reachable() const - { - return turns < 255; - } - - bool isTeleportAction() const - { - if (action != EPathNodeAction::TELEPORT_NORMAL && - action != EPathNodeAction::TELEPORT_BLOCKING_VISIT && - action != EPathNodeAction::TELEPORT_BATTLE) - { - return false; - } - - return true; - } -}; - -struct DLL_LINKAGE CGPath -{ - std::vector nodes; //just get node by node - - /// Starting position of path, matches location of hero - const CGPathNode & currNode() const; - /// First node in path, this is where hero will move next - const CGPathNode & nextNode() const; - /// Last node in path, this is what hero wants to reach in the end - const CGPathNode & lastNode() const; - - int3 startPos() const; // start point - int3 endPos() const; //destination point -}; - -struct DLL_LINKAGE CPathsInfo -{ - using ELayer = EPathfindingLayer; - - const CGHeroInstance * hero; - int3 hpos; - int3 sizes; - boost::multi_array nodes; //[layer][level][w][h] - - CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); - ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 & tile) const; - bool getPath(CGPath & out, const int3 & dst) const; - const CGPathNode * getNode(const int3 & coord) const; - - STRONG_INLINE - CGPathNode * getNode(const int3 & coord, const ELayer layer) - { - return &nodes[layer.getNum()][coord.z][coord.x][coord.y]; - } -}; - -struct DLL_LINKAGE PathNodeInfo -{ - CGPathNode * node; - const CGObjectInstance * nodeObject; - const CGHeroInstance * nodeHero; - const TerrainTile * tile; - int3 coord; - bool guarded; - PlayerRelations objectRelations; - PlayerRelations heroRelations; - bool isInitialPosition; - - PathNodeInfo(); - - virtual void setNode(CGameState * gs, CGPathNode * n); - - void updateInfo(CPathfinderHelper * hlp, CGameState * gs); - - bool isNodeObjectVisitable() const; -}; - -struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo -{ - EPathNodeAction action; - int turn; - int movementLeft; - float cost; //same as CGPathNode::cost - bool blocked; - bool isGuardianTile; - - CDestinationNodeInfo(); - - void setNode(CGameState * gs, CGPathNode * n) override; - - virtual bool isBetterWay() const; -}; - -VCMI_LIB_NAMESPACE_END +/* + * CGPathNode.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../GameConstants.h" +#include "../int3.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +class CGHeroInstance; +class CGObjectInstance; +class CGameState; +class CPathfinderHelper; +struct TerrainTile; + +template +struct DLL_LINKAGE NodeComparer +{ + STRONG_INLINE + bool operator()(const N * lhs, const N * rhs) const + { + return lhs->getCost() > rhs->getCost(); + } +}; + +enum class EPathAccessibility : ui8 +{ + NOT_SET, + ACCESSIBLE, //tile can be entered and passed + VISITABLE, //tile can be entered as the last tile in path + GUARDED, //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any) + BLOCKVIS, //visitable from neighbouring tile but not passable + FLYABLE, //can only be accessed in air layer + BLOCKED //tile can be neither entered nor visited +}; + +enum class EPathNodeAction : ui8 +{ + UNKNOWN, + EMBARK, + DISEMBARK, + NORMAL, + BATTLE, + VISIT, + BLOCKING_VISIT, + TELEPORT_NORMAL, + TELEPORT_BLOCKING_VISIT, + TELEPORT_BATTLE +}; + +struct DLL_LINKAGE CGPathNode +{ + using TFibHeap = boost::heap::fibonacci_heap>>; + using ELayer = EPathfindingLayer; + + TFibHeap::handle_type pqHandle; + TFibHeap * pq; + CGPathNode * theNodeBefore; + + int3 coord; //coordinates + ELayer layer; + + float cost; //total cost of the path to this tile measured in turns with fractions + int moveRemains; //remaining movement points after hero reaches the tile + ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn + EPathAccessibility accessible; + EPathNodeAction action; + bool locked; + + CGPathNode() + : coord(-1), + layer(ELayer::WRONG), + pqHandle(nullptr) + { + reset(); + } + + STRONG_INLINE + void reset() + { + locked = false; + accessible = EPathAccessibility::NOT_SET; + moveRemains = 0; + cost = std::numeric_limits::max(); + turns = 255; + theNodeBefore = nullptr; + pq = nullptr; + action = EPathNodeAction::UNKNOWN; + } + + STRONG_INLINE + bool inPQ() const + { + return pq != nullptr; + } + + STRONG_INLINE + float getCost() const + { + return cost; + } + + STRONG_INLINE + void setCost(float value) + { + if(vstd::isAlmostEqual(value, cost)) + return; + + bool getUpNode = value < cost; + cost = value; + // If the node is in the heap, update the heap. + if(inPQ()) + { + if(getUpNode) + { + pq->increase(this->pqHandle); + } + else + { + pq->decrease(this->pqHandle); + } + } + } + + STRONG_INLINE + void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible) + { + if(layer == ELayer::WRONG) + { + coord = Coord; + layer = Layer; + } + else + { + reset(); + } + + accessible = Accessible; + } + + STRONG_INLINE + bool reachable() const + { + return turns < 255; + } + + bool isTeleportAction() const + { + if (action != EPathNodeAction::TELEPORT_NORMAL && + action != EPathNodeAction::TELEPORT_BLOCKING_VISIT && + action != EPathNodeAction::TELEPORT_BATTLE) + { + return false; + } + + return true; + } +}; + +struct DLL_LINKAGE CGPath +{ + std::vector nodes; //just get node by node + + /// Starting position of path, matches location of hero + const CGPathNode & currNode() const; + /// First node in path, this is where hero will move next + const CGPathNode & nextNode() const; + /// Last node in path, this is what hero wants to reach in the end + const CGPathNode & lastNode() const; + + int3 startPos() const; // start point + int3 endPos() const; //destination point +}; + +struct DLL_LINKAGE CPathsInfo +{ + using ELayer = EPathfindingLayer; + + const CGHeroInstance * hero; + int3 hpos; + int3 sizes; + boost::multi_array nodes; //[layer][level][w][h] + + CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); + ~CPathsInfo(); + const CGPathNode * getPathInfo(const int3 & tile) const; + bool getPath(CGPath & out, const int3 & dst) const; + const CGPathNode * getNode(const int3 & coord) const; + + STRONG_INLINE + CGPathNode * getNode(const int3 & coord, const ELayer layer) + { + return &nodes[layer.getNum()][coord.z][coord.x][coord.y]; + } +}; + +struct DLL_LINKAGE PathNodeInfo +{ + CGPathNode * node; + const CGObjectInstance * nodeObject; + const CGHeroInstance * nodeHero; + const TerrainTile * tile; + int3 coord; + bool guarded; + PlayerRelations objectRelations; + PlayerRelations heroRelations; + bool isInitialPosition; + + PathNodeInfo(); + + virtual void setNode(CGameState * gs, CGPathNode * n); + + void updateInfo(CPathfinderHelper * hlp, CGameState * gs); + + bool isNodeObjectVisitable() const; +}; + +struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo +{ + EPathNodeAction action; + int turn; + int movementLeft; + float cost; //same as CGPathNode::cost + bool blocked; + bool isGuardianTile; + + CDestinationNodeInfo(); + + void setNode(CGameState * gs, CGPathNode * n) override; + + virtual bool isBetterWay() const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 29b1acfc6..2ae643a59 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -1,1027 +1,1027 @@ -/* - * CZonePlacer.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "CZonePlacer.h" - -#include "../TerrainHandler.h" -#include "../entities/faction/CFaction.h" -#include "../entities/faction/CTownHandler.h" -#include "../mapping/CMap.h" -#include "../mapping/CMapEditManager.h" -#include "../VCMI_Lib.h" -#include "CMapGenOptions.h" -#include "RmgMap.h" -#include "Zone.h" -#include "Functions.h" -#include "PenroseTiling.h" - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -//#define ZONE_PLACEMENT_LOG true - -CZonePlacer::CZonePlacer(RmgMap & map) - : width(0), height(0), mapSize(0), - gravityConstant(1e-3f), - stiffnessConstant(3e-3f), - stifness(0), - stiffnessIncreaseFactor(1.03f), - bestTotalDistance(1e10), - bestTotalOverlap(1e10), - map(map) -{ -} - -int3 CZonePlacer::cords(const float3 & f) const -{ - return int3(static_cast(std::max(0.f, (f.x * map.width()) - 1)), static_cast(std::max(0.f, (f.y * map.height() - 1))), f.z); -} - -float CZonePlacer::getDistance (float distance) const -{ - return (distance ? distance * distance : 1e-6f); -} - -void CZonePlacer::findPathsBetweenZones() -{ - auto zones = map.getZones(); - - std::set> zonesToCheck; - - // Iterate through each pair of nodes in the graph - - for (const auto& zone : zones) - { - int start = zone.first; - distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 - - std::queue q; - std::map visited; - visited[start] = true; - q.push(start); - - // Perform Breadth-First Search from the starting node - while (!q.empty()) - { - int current = q.front(); - q.pop(); - - const auto& currentZone = zones.at(current); - const auto& connectedZoneIds = currentZone->getConnections(); - - for (auto & connection : connectedZoneIds) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - auto neighbor = connection.getOtherZoneId(current); - - if (current == neighbor) - { - //Do not consider self-connections - continue; - } - - if (!visited[neighbor]) - { - visited[neighbor] = true; - q.push(neighbor); - distancesBetweenZones[start][neighbor] = distancesBetweenZones[start][current] + 1; - } - } - } - } -} - -void CZonePlacer::placeOnGrid(vstd::RNG* rand) -{ - auto zones = map.getZones(); - assert(zones.size()); - - //Make sure there are at least as many grid fields as the number of zones - size_t gridSize = std::ceil(std::sqrt(zones.size())); - - typedef boost::multi_array, 2> GridType; - GridType grid(boost::extents[gridSize][gridSize]); - - TZoneVector zonesVector(zones.begin(), zones.end()); - - //Place first zone - - auto firstZone = zonesVector[0].second; - size_t x = 0; - size_t y = 0; - - auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y) - { - switch (rand->nextInt(0, 3) % 4) - { - case 0: - x = 0; - y = gridSize / 2; - break; - case 1: - x = gridSize - 1; - y = gridSize / 2; - break; - case 2: - x = gridSize / 2; - y = 0; - break; - case 3: - x = gridSize / 2; - y = gridSize - 1; - break; - } - }; - - switch (firstZone->getType()) - { - case ETemplateZoneType::PLAYER_START: - case ETemplateZoneType::CPU_START: - if (firstZone->getConnectedZoneIds().size() > 2) - { - getRandomEdge(x, y); - } - else - { - //Random corner - if (rand->nextInt(0, 1) == 1) - { - x = 0; - } - else - { - x = gridSize - 1; - } - if (rand->nextInt(0, 1) == 1) - { - y = 0; - } - else - { - y = gridSize - 1; - } - } - break; - case ETemplateZoneType::TREASURE: - if (gridSize & 1) //odd - { - x = y = (gridSize / 2); - } - else - { - //One of 4 squares in the middle - x = (gridSize / 2) - 1 + rand->nextInt(0, 1); - y = (gridSize / 2) - 1 + rand->nextInt(0, 1); - } - break; - case ETemplateZoneType::JUNCTION: - getRandomEdge(x, y); - break; - } - grid[x][y] = firstZone; - - //Ignore z placement for simplicity - - for (size_t i = 1; i < zones.size(); i++) - { - auto zone = zonesVector[i].second; - auto connectedZoneIds = zone->getConnectedZoneIds(); - - float maxDistance = -1000.0; - int3 mostDistantPlace; - - //Iterate over free positions - for (size_t freeX = 0; freeX < gridSize; ++freeX) - { - for (size_t freeY = 0; freeY < gridSize; ++freeY) - { - if (!grid[freeX][freeY]) - { - //There is free space left here - int3 potentialPos(freeX, freeY, 0); - - //Compute distance to every existing zone - - float distance = 0; - for (size_t existingX = 0; existingX < gridSize; ++existingX) - { - for (size_t existingY = 0; existingY < gridSize; ++existingY) - { - auto existingZone = grid[existingX][existingY]; - if (existingZone) - { - //There is already zone here - float localDistance = 0.0f; - - auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()]; - if (graphDistance > 1) - { - //No direct connection - localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance; - } - else - { - //Has direct connection - place as close as possible - localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0)); - } - - localDistance *= scaleForceBetweenZones(zone, existingZone); - - distance += localDistance; - } - } - } - if (distance > maxDistance) - { - maxDistance = distance; - mostDistantPlace = potentialPos; - } - } - } - } - - //Place in a free slot - grid[mostDistantPlace.x][mostDistantPlace.y] = zone; - } - - //TODO: toggle with a flag -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Initial zone grid:"); - for (size_t x = 0; x < gridSize; ++x) - { - std::string s; - for (size_t y = 0; y < gridSize; ++y) - { - if (grid[x][y]) - { - s += (boost::format("%3d ") % grid[x][y]->getId()).str(); - } - else - { - s += " -- "; - } - } - logGlobal->trace(s); - } -#endif - - //Set initial position for zones - random position in square centered around (x, y) - for (size_t x = 0; x < gridSize; ++x) - { - for (size_t y = 0; y < gridSize; ++y) - { - auto zone = grid[x][y]; - if (zone) - { - //i.e. for grid size 5 we get range (0.25 - 4.75) - auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f); - vstd::abetween(targetX, 0.5, gridSize - 0.5); - auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f); - vstd::abetween(targetY, 0.5, gridSize - 0.5); - - zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); - } - } - } -} - -float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const -{ - if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game - { - int firstPlayer = zoneA->getOwner().value(); - int secondPlayer = zoneB->getOwner().value(); - - //Players with lower indexes (especially 1 and 2) will be placed further apart - - return (1.0f + (2.0f / (firstPlayer * secondPlayer))); - } - else - { - return 1; - } -} - -void CZonePlacer::placeZones(vstd::RNG * rand) -{ - logGlobal->info("Starting zone placement"); - - width = map.getMapGenOptions().getWidth(); - height = map.getMapGenOptions().getHeight(); - - auto zones = map.getZones(); - vstd::erase_if(zones, [](const std::pair> & pr) - { - return pr.second->getType() == ETemplateZoneType::WATER; - }); - bool underground = map.getMapGenOptions().getHasTwoLevels(); - - findPathsBetweenZones(); - placeOnGrid(rand); - - /* - Fruchterman-Reingold algorithm - - Let's assume we try to fit N circular zones with radius = size on a map - Connected zones attract, intersecting zones and map boundaries push back - */ - - TZoneVector zonesVector(zones.begin(), zones.end()); - assert (zonesVector.size()); - - RandomGeneratorUtil::randomShuffle(zonesVector, *rand); - - //0. set zone sizes and surface / underground level - prepareZones(zones, zonesVector, underground, rand); - - std::map, float3> bestSolution; - - TForceVector forces; - TForceVector totalForces; // both attraction and pushback, overcomplicated? - TDistanceVector distances; - TDistanceVector overlaps; - - auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool - { - bool improvement = false; - - float totalDistance = 0; - float totalOverlap = 0; - for (const auto& zone : distances) //find most misplaced zone - { - totalDistance += zone.second; - float overlap = overlaps[zone.first]; - totalOverlap += overlap; - } - - //check fitness function - if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1)) - { - //multiplication is better for auto-scaling, but stops working if one factor is 0 - improvement = true; - } - - //Save best solution - if (improvement) - { - bestTotalDistance = totalDistance; - bestTotalOverlap = totalOverlap; - - for (const auto& zone : zones) - bestSolution[zone.second] = zone.second->getCenter(); - } - -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); -#endif - - return improvement; - }; - - //Start with low stiffness. Bigger graphs need more time and more flexibility - for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;) - { - //1. attract connected zones - attractConnectedZones(zones, forces, distances); - for(const auto & zone : forces) - { - zone.first->setCenter (zone.first->getCenter() + zone.second); - totalForces[zone.first] = zone.second; //override - } - - //2. separate overlapping zones - separateOverlappingZones(zones, forces, overlaps); - for(const auto & zone : forces) - { - zone.first->setCenter (zone.first->getCenter() + zone.second); - totalForces[zone.first] += zone.second; //accumulate - } - - bool improved = evaluateSolution(); - - if (!improved) - { - //3. now perform drastic movement of zone that is completely not linked - //TODO: Don't do this is fitness was improved - moveOneZone(zones, totalForces, distances, overlaps); - - improved |= evaluateSolution(); - } - - if (!improved) - { - //Only cool down if we didn't see any improvement - stifness *= stiffnessIncreaseFactor; - } - - } - - logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap); - for(const auto & zone : zones) //finalize zone positions - { - zone.second->setPos (cords (bestSolution[zone.second])); -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString()); -#endif - } -} - -void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) -{ - std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map - - int zonesOnLevel[2] = { 0, 0 }; - - //even distribution for surface / underground zones. Surface zones always have priority. - - TZoneVector zonesToPlace; - std::map levels; - - //first pass - determine fixed surface for zones - for(const auto & zone : zonesVector) - { - if (!underground) //this step is ignored - zonesToPlace.push_back(zone); - else //place players depending on their factions - { - if(std::optional owner = zone.second->getOwner()) - { - auto player = PlayerColor(*owner - 1); - auto playerSettings = map.getMapGenOptions().getPlayersSettings(); - FactionID faction = FactionID::RANDOM; - if (playerSettings.size() > player) - { - faction = std::next(playerSettings.begin(), player)->second.getStartingTown(); - } - else - { - logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first); - } - - if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized - zonesToPlace.push_back(zone); - else - { - auto & tt = (*VLC->townh)[faction]->nativeTerrain; - if(tt == ETerrainId::NONE) - { - //any / random - zonesToPlace.push_back(zone); - } - else - { - const auto & terrainType = VLC->terrainTypeHandler->getById(tt); - if(terrainType->isUnderground() && !terrainType->isSurface()) - { - //underground only - zonesOnLevel[1]++; - levels[zone.first] = 1; - } - else - { - //surface - zonesOnLevel[0]++; - levels[zone.first] = 0; - } - } - } - } - else //no starting zone or no underground altogether - { - zonesToPlace.push_back(zone); - } - } - } - for(const auto & zone : zonesToPlace) - { - if (underground) //only then consider underground zones - { - int level = 0; - if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones - level = 1; - else - level = 0; - - levels[zone.first] = level; - zonesOnLevel[level]++; - } - else - levels[zone.first] = 0; - } - - for(const auto & zone : zonesVector) - { - int level = levels[zone.first]; - totalSize[level] += (zone.second->getSize() * zone.second->getSize()); - float3 center = zone.second->getCenter(); - center.z = level; - zone.second->setCenter(center); - } - - /* - prescale zones - - formula: sum((prescaler*n)^2)*pi = WH - - prescaler = sqrt((WH)/(sum(n^2)*pi)) - */ - - std::vector prescaler = { 0, 0 }; - for (int i = 0; i < 2; i++) - prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); - mapSize = static_cast(sqrt(width * height)); - for(const auto & zone : zones) - { - zone.second->setSize(static_cast(zone.second->getSize() * prescaler[zone.second->getCenter().z])); - } -} - -void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const -{ - for(const auto & zone : zones) - { - float3 forceVector(0, 0, 0); - float3 pos = zone.second->getCenter(); - float totalDistance = 0; - - for (const auto & connection : zone.second->getConnections()) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - if (connection.getZoneA() == connection.getZoneB()) - { - //Do not consider self-connections - continue; - } - - auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; - float3 otherZoneCenter = otherZone->getCenter(); - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - - forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value - - //Attract zone centers always - - float minDistance = 0; - - if (pos.z != otherZoneCenter.z) - minDistance = 0; //zones on different levels can overlap completely - else - minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates - - if (distance > minDistance) - totalDistance += (distance - minDistance); - } - distances[zone.second] = totalDistance; - forceVector.z = 0; //operator - doesn't preserve z coordinate :/ - forces[zone.second] = forceVector; - } -} - -void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps) -{ - for(const auto & zone : zones) - { - float3 forceVector(0, 0, 0); - float3 pos = zone.second->getCenter(); - - float overlap = 0; - //separate overlapping zones - for(const auto & otherZone : zones) - { - float3 otherZoneCenter = otherZone.second->getCenter(); - //zones on different levels don't push away - if (zone == otherZone || pos.z != otherZoneCenter.z) - continue; - - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; - if (distance < minDistance) - { - float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; - //negative value - localForce *= scaleForceBetweenZones(zone.second, otherZone.second); - forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); - overlap += (minDistance - distance); //overlapping of small zones hurts us more - } - } - - //move zones away from boundaries - //do not scale boundary distance - zones tend to get squashed - float size = zone.second->getSize() / mapSize; - - auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y) - { - float3 boundary = float3(x, y, pos.z); - auto distance = static_cast(pos.dist2d(boundary)); - overlap += std::max(0, distance - size); //check if we're closer to map boundary than value of zone size - forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value - }; - if (pos.x < size) - { - pushAwayFromBoundary(0, pos.y); - } - if (pos.x > 1 - size) - { - pushAwayFromBoundary(1, pos.y); - } - if (pos.y < size) - { - pushAwayFromBoundary(pos.x, 0); - } - if (pos.y > 1 - size) - { - pushAwayFromBoundary(pos.x, 1); - } - - //Always move repulsive zones away, no matter their distance - //TODO: Consider z plane? - for (auto& connection : zone.second->getConnections()) - { - if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) - { - auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; - float3 otherZoneCenter = otherZone->getCenter(); - - //TODO: Roll into lambda? - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; - float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; - localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]); - forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone); - } - } - - overlaps[zone.second] = overlap; - forceVector.z = 0; //operator - doesn't preserve z coordinate :/ - forces[zone.second] = forceVector; - } -} - -void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps) -{ - //The more zones, the greater total distance expected - //Also, higher stiffness make expected movement lower - const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness); - - typedef std::pair> Misplacement; - std::vector misplacedZones; - - float totalDistance = 0; - float totalOverlap = 0; - for (const auto& zone : distances) //find most misplaced zone - { - if (vstd::contains(lastSwappedZones, zone.first->getId())) - { - continue; - } - totalDistance += zone.second; - float overlap = overlaps[zone.first]; - totalOverlap += overlap; - //if distance to actual movement is long, the zone is misplaced - float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); - if (ratio > maxDistanceMovementRatio) - { - misplacedZones.emplace_back(std::make_pair(ratio, zone.first)); - } - } - - if (misplacedZones.empty()) - return; - - boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs) - { - return lhs.first > rhs.first; //Largest displacement first - }); - -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); -#endif - - if (misplacedZones.size() >= 2) - { - //Swap 2 misplaced zones - - auto firstZone = misplacedZones.front().second; - std::shared_ptr secondZone; - std::set connectedZones; - for (const auto& connection : firstZone->getConnections()) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - if (connection.getZoneA() == connection.getZoneB()) - { - //Do not consider self-connections - continue; - } - connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); - } - - auto level = firstZone->getCenter().z; - for (size_t i = 1; i < misplacedZones.size(); i++) - { - //Only swap zones on the same level - //Don't swap zones that should be connected (Jebus) - - if (misplacedZones[i].second->getCenter().z == level && - !vstd::contains(connectedZones, misplacedZones[i].second->getId())) - { - secondZone = misplacedZones[i].second; - break; - } - } - if (secondZone) - { -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); -#endif - - auto firstCenter = firstZone->getCenter(); - auto secondCenter = secondZone->getCenter(); - firstZone->setCenter(secondCenter); - secondZone->setCenter(firstCenter); - - lastSwappedZones.insert(firstZone->getId()); - lastSwappedZones.insert(secondZone->getId()); - return; - } - } - lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next - - //find most distant zone that should be attracted and move inside it - std::shared_ptr targetZone; - auto misplacedZone = misplacedZones.front().second; - float3 ourCenter = misplacedZone->getCenter(); - - if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1))) - { - //Move one zone towards most distant zone to reduce distance - - float maxDistance = 0; - for (auto con : misplacedZone->getConnections()) - { - if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE) - { - continue; - } - - auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())]; - float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); - if (distance > maxDistance) - { - maxDistance = distance; - targetZone = otherZone; - } - } - if (targetZone) - { - float3 vec = targetZone->getCenter() - ourCenter; - float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); -#endif - - misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size - } - } - else - { - //Move misplaced zone away from overlapping zone - - float maxOverlap = 0; - for(const auto & otherZone : zones) - { - float3 otherZoneCenter = otherZone.second->getCenter(); - - if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) - continue; - - auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); - if (distance > maxOverlap) - { - maxOverlap = distance; - targetZone = otherZone.second; - } - } - if (targetZone) - { - float3 vec = ourCenter - targetZone->getCenter(); - float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); -#endif - - misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated - } - } - //Don't swap that zone in next iteration - lastSwappedZones.insert(misplacedZone->getId()); -} - -float CZonePlacer::metric (const int3 &A, const int3 &B) const -{ - return A.dist2dSQ(B); - -} - -void CZonePlacer::assignZones(vstd::RNG * rand) -{ - logGlobal->info("Starting zone colouring"); - - auto width = map.getMapGenOptions().getWidth(); - auto height = map.getMapGenOptions().getHeight(); - - - auto zones = map.getZones(); - vstd::erase_if(zones, [](const std::pair> & pr) - { - return pr.second->getType() == ETemplateZoneType::WATER; - }); - - using Dpair = std::pair, float>; - std::vector distances; - distances.reserve(zones.size()); - - //now place zones correctly and assign tiles to each zone - - auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool - { - //bigger zones have smaller distance - return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); - }; - - auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool - { - //bigger zones have smaller distance - return lhs.second < rhs.second; - }; - - auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr & zone) -> void - { - int3 total(0, 0, 0); - auto tiles = zone->area()->getTiles(); - for(const auto & tile : tiles) - { - total += tile; - } - int size = static_cast(tiles.size()); - assert(size); - auto newPos = int3(total.x / size, total.y / size, total.z / size); - zone->setPos(newPos); - zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z)); - }; - - int levels = map.levels(); - - // Find current center of mass for each zone. Move zone to that center to balance zones sizes - std::vector zonesOnLevel; - for(int level = 0; level < levels; level++) - { - zonesOnLevel.push_back(map.getZonesOnLevel(level)); - } - - int3 pos; - - for(pos.z = 0; pos.z < levels; pos.z++) - { - for(pos.x = 0; pos.x < width; pos.x++) - { - for(pos.y = 0; pos.y < height; pos.y++) - { - distances.clear(); - for(const auto & zone : zonesOnLevel[pos.z]) - { - distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); - } - boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone - } - } - } - - for(const auto & zone : zones) - { - if(zone.second->area()->empty()) - throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); - - moveZoneToCenterOfMass(zone.second); - } - - for(const auto & zone : zones) - zone.second->clearTiles(); //now populate them again - - PenroseTiling penrose; - for (int level = 0; level < levels; level++) - { - //Create different tiling for each level - - auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand); - - // Assign zones to closest Penrose vertex - std::map, std::set> vertexMapping; - - for (const auto & vertex : vertices) - { - distances.clear(); - for(const auto & zone : zonesOnLevel[level]) - { - distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); - } - auto closestZone = boost::min_element(distances, compareByDistance)->first; - - vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone - } - - //Assign actual tiles to each zone - pos.z = level; - for (pos.x = 0; pos.x < width; pos.x++) - { - for (pos.y = 0; pos.y < height; pos.y++) - { - distances.clear(); - for(const auto & zoneVertex : vertexMapping) - { - auto zone = zoneVertex.first; - for (const auto & vertex : zoneVertex.second) - { - distances.emplace_back(zone, metric(pos, vertex)); - } - } - - //Tile closest to vertex belongs to zone - auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; - closestZone->area()->add(pos); - map.setZoneID(pos, closestZone->getId()); - } - } - - for(const auto & zone : zonesOnLevel[level]) - { - if(zone.second->area()->empty()) - { - // FIXME: Some vertices are duplicated, but it's not a source of problem - logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); - for (const auto & vertex : vertices) - { - logGlobal->warn("Penrose Vertex: %s", vertex.toString()); - } - throw rmgException("Empty zone after Penrose tiling"); - } - } - } - - //set position (town position) to center of mass of irregular zone - for(const auto & zone : zones) - { - moveZoneToCenterOfMass(zone.second); - - //TODO: similar for islands - #define CREATE_FULL_UNDERGROUND true //consider linking this with water amount - if (zone.second->isUnderground()) - { - if (!CREATE_FULL_UNDERGROUND) - { - auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f); - for(const auto & t : discardTiles) - zone.second->area()->erase(t); - } - - //make sure that terrain inside zone is not a rock - - auto v = zone.second->area()->getTilesVector(); - map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN); - } - } - logGlobal->info("Finished zone colouring"); -} - -const TDistanceMap& CZonePlacer::getDistanceMap() -{ - return distancesBetweenZones; -} - -VCMI_LIB_NAMESPACE_END +/* + * CZonePlacer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CZonePlacer.h" + +#include "../TerrainHandler.h" +#include "../entities/faction/CFaction.h" +#include "../entities/faction/CTownHandler.h" +#include "../mapping/CMap.h" +#include "../mapping/CMapEditManager.h" +#include "../VCMI_Lib.h" +#include "CMapGenOptions.h" +#include "RmgMap.h" +#include "Zone.h" +#include "Functions.h" +#include "PenroseTiling.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +//#define ZONE_PLACEMENT_LOG true + +CZonePlacer::CZonePlacer(RmgMap & map) + : width(0), height(0), mapSize(0), + gravityConstant(1e-3f), + stiffnessConstant(3e-3f), + stifness(0), + stiffnessIncreaseFactor(1.03f), + bestTotalDistance(1e10), + bestTotalOverlap(1e10), + map(map) +{ +} + +int3 CZonePlacer::cords(const float3 & f) const +{ + return int3(static_cast(std::max(0.f, (f.x * map.width()) - 1)), static_cast(std::max(0.f, (f.y * map.height() - 1))), f.z); +} + +float CZonePlacer::getDistance (float distance) const +{ + return (distance ? distance * distance : 1e-6f); +} + +void CZonePlacer::findPathsBetweenZones() +{ + auto zones = map.getZones(); + + std::set> zonesToCheck; + + // Iterate through each pair of nodes in the graph + + for (const auto& zone : zones) + { + int start = zone.first; + distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 + + std::queue q; + std::map visited; + visited[start] = true; + q.push(start); + + // Perform Breadth-First Search from the starting node + while (!q.empty()) + { + int current = q.front(); + q.pop(); + + const auto& currentZone = zones.at(current); + const auto& connectedZoneIds = currentZone->getConnections(); + + for (auto & connection : connectedZoneIds) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + auto neighbour = connection.getOtherZoneId(current); + + if (current == neighbour) + { + //Do not consider self-connections + continue; + } + + if (!visited[neighbour]) + { + visited[neighbour] = true; + q.push(neighbour); + distancesBetweenZones[start][neighbour] = distancesBetweenZones[start][current] + 1; + } + } + } + } +} + +void CZonePlacer::placeOnGrid(vstd::RNG* rand) +{ + auto zones = map.getZones(); + assert(zones.size()); + + //Make sure there are at least as many grid fields as the number of zones + size_t gridSize = std::ceil(std::sqrt(zones.size())); + + typedef boost::multi_array, 2> GridType; + GridType grid(boost::extents[gridSize][gridSize]); + + TZoneVector zonesVector(zones.begin(), zones.end()); + + //Place first zone + + auto firstZone = zonesVector[0].second; + size_t x = 0; + size_t y = 0; + + auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y) + { + switch (rand->nextInt(0, 3) % 4) + { + case 0: + x = 0; + y = gridSize / 2; + break; + case 1: + x = gridSize - 1; + y = gridSize / 2; + break; + case 2: + x = gridSize / 2; + y = 0; + break; + case 3: + x = gridSize / 2; + y = gridSize - 1; + break; + } + }; + + switch (firstZone->getType()) + { + case ETemplateZoneType::PLAYER_START: + case ETemplateZoneType::CPU_START: + if (firstZone->getConnectedZoneIds().size() > 2) + { + getRandomEdge(x, y); + } + else + { + //Random corner + if (rand->nextInt(0, 1) == 1) + { + x = 0; + } + else + { + x = gridSize - 1; + } + if (rand->nextInt(0, 1) == 1) + { + y = 0; + } + else + { + y = gridSize - 1; + } + } + break; + case ETemplateZoneType::TREASURE: + if (gridSize & 1) //odd + { + x = y = (gridSize / 2); + } + else + { + //One of 4 squares in the middle + x = (gridSize / 2) - 1 + rand->nextInt(0, 1); + y = (gridSize / 2) - 1 + rand->nextInt(0, 1); + } + break; + case ETemplateZoneType::JUNCTION: + getRandomEdge(x, y); + break; + } + grid[x][y] = firstZone; + + //Ignore z placement for simplicity + + for (size_t i = 1; i < zones.size(); i++) + { + auto zone = zonesVector[i].second; + auto connectedZoneIds = zone->getConnectedZoneIds(); + + float maxDistance = -1000.0; + int3 mostDistantPlace; + + //Iterate over free positions + for (size_t freeX = 0; freeX < gridSize; ++freeX) + { + for (size_t freeY = 0; freeY < gridSize; ++freeY) + { + if (!grid[freeX][freeY]) + { + //There is free space left here + int3 potentialPos(freeX, freeY, 0); + + //Compute distance to every existing zone + + float distance = 0; + for (size_t existingX = 0; existingX < gridSize; ++existingX) + { + for (size_t existingY = 0; existingY < gridSize; ++existingY) + { + auto existingZone = grid[existingX][existingY]; + if (existingZone) + { + //There is already zone here + float localDistance = 0.0f; + + auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()]; + if (graphDistance > 1) + { + //No direct connection + localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance; + } + else + { + //Has direct connection - place as close as possible + localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0)); + } + + localDistance *= scaleForceBetweenZones(zone, existingZone); + + distance += localDistance; + } + } + } + if (distance > maxDistance) + { + maxDistance = distance; + mostDistantPlace = potentialPos; + } + } + } + } + + //Place in a free slot + grid[mostDistantPlace.x][mostDistantPlace.y] = zone; + } + + //TODO: toggle with a flag +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Initial zone grid:"); + for (size_t x = 0; x < gridSize; ++x) + { + std::string s; + for (size_t y = 0; y < gridSize; ++y) + { + if (grid[x][y]) + { + s += (boost::format("%3d ") % grid[x][y]->getId()).str(); + } + else + { + s += " -- "; + } + } + logGlobal->trace(s); + } +#endif + + //Set initial position for zones - random position in square centered around (x, y) + for (size_t x = 0; x < gridSize; ++x) + { + for (size_t y = 0; y < gridSize; ++y) + { + auto zone = grid[x][y]; + if (zone) + { + //i.e. for grid size 5 we get range (0.25 - 4.75) + auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f); + vstd::abetween(targetX, 0.5, gridSize - 0.5); + auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f); + vstd::abetween(targetY, 0.5, gridSize - 0.5); + + zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); + } + } + } +} + +float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const +{ + if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game + { + int firstPlayer = zoneA->getOwner().value(); + int secondPlayer = zoneB->getOwner().value(); + + //Players with lower indexes (especially 1 and 2) will be placed further apart + + return (1.0f + (2.0f / (firstPlayer * secondPlayer))); + } + else + { + return 1; + } +} + +void CZonePlacer::placeZones(vstd::RNG * rand) +{ + logGlobal->info("Starting zone placement"); + + width = map.getMapGenOptions().getWidth(); + height = map.getMapGenOptions().getHeight(); + + auto zones = map.getZones(); + vstd::erase_if(zones, [](const std::pair> & pr) + { + return pr.second->getType() == ETemplateZoneType::WATER; + }); + bool underground = map.getMapGenOptions().getHasTwoLevels(); + + findPathsBetweenZones(); + placeOnGrid(rand); + + /* + Fruchterman-Reingold algorithm + + Let's assume we try to fit N circular zones with radius = size on a map + Connected zones attract, intersecting zones and map boundaries push back + */ + + TZoneVector zonesVector(zones.begin(), zones.end()); + assert (zonesVector.size()); + + RandomGeneratorUtil::randomShuffle(zonesVector, *rand); + + //0. set zone sizes and surface / underground level + prepareZones(zones, zonesVector, underground, rand); + + std::map, float3> bestSolution; + + TForceVector forces; + TForceVector totalForces; // both attraction and pushback, overcomplicated? + TDistanceVector distances; + TDistanceVector overlaps; + + auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool + { + bool improvement = false; + + float totalDistance = 0; + float totalOverlap = 0; + for (const auto& zone : distances) //find most misplaced zone + { + totalDistance += zone.second; + float overlap = overlaps[zone.first]; + totalOverlap += overlap; + } + + //check fitness function + if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1)) + { + //multiplication is better for auto-scaling, but stops working if one factor is 0 + improvement = true; + } + + //Save best solution + if (improvement) + { + bestTotalDistance = totalDistance; + bestTotalOverlap = totalOverlap; + + for (const auto& zone : zones) + bestSolution[zone.second] = zone.second->getCenter(); + } + +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); +#endif + + return improvement; + }; + + //Start with low stiffness. Bigger graphs need more time and more flexibility + for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;) + { + //1. attract connected zones + attractConnectedZones(zones, forces, distances); + for(const auto & zone : forces) + { + zone.first->setCenter (zone.first->getCenter() + zone.second); + totalForces[zone.first] = zone.second; //override + } + + //2. separate overlapping zones + separateOverlappingZones(zones, forces, overlaps); + for(const auto & zone : forces) + { + zone.first->setCenter (zone.first->getCenter() + zone.second); + totalForces[zone.first] += zone.second; //accumulate + } + + bool improved = evaluateSolution(); + + if (!improved) + { + //3. now perform drastic movement of zone that is completely not linked + //TODO: Don't do this is fitness was improved + moveOneZone(zones, totalForces, distances, overlaps); + + improved |= evaluateSolution(); + } + + if (!improved) + { + //Only cool down if we didn't see any improvement + stifness *= stiffnessIncreaseFactor; + } + + } + + logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap); + for(const auto & zone : zones) //finalize zone positions + { + zone.second->setPos (cords (bestSolution[zone.second])); +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString()); +#endif + } +} + +void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) +{ + std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map + + int zonesOnLevel[2] = { 0, 0 }; + + //even distribution for surface / underground zones. Surface zones always have priority. + + TZoneVector zonesToPlace; + std::map levels; + + //first pass - determine fixed surface for zones + for(const auto & zone : zonesVector) + { + if (!underground) //this step is ignored + zonesToPlace.push_back(zone); + else //place players depending on their factions + { + if(std::optional owner = zone.second->getOwner()) + { + auto player = PlayerColor(*owner - 1); + auto playerSettings = map.getMapGenOptions().getPlayersSettings(); + FactionID faction = FactionID::RANDOM; + if (playerSettings.size() > player) + { + faction = std::next(playerSettings.begin(), player)->second.getStartingTown(); + } + else + { + logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first); + } + + if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized + zonesToPlace.push_back(zone); + else + { + auto & tt = (*VLC->townh)[faction]->nativeTerrain; + if(tt == ETerrainId::NONE) + { + //any / random + zonesToPlace.push_back(zone); + } + else + { + const auto & terrainType = VLC->terrainTypeHandler->getById(tt); + if(terrainType->isUnderground() && !terrainType->isSurface()) + { + //underground only + zonesOnLevel[1]++; + levels[zone.first] = 1; + } + else + { + //surface + zonesOnLevel[0]++; + levels[zone.first] = 0; + } + } + } + } + else //no starting zone or no underground altogether + { + zonesToPlace.push_back(zone); + } + } + } + for(const auto & zone : zonesToPlace) + { + if (underground) //only then consider underground zones + { + int level = 0; + if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones + level = 1; + else + level = 0; + + levels[zone.first] = level; + zonesOnLevel[level]++; + } + else + levels[zone.first] = 0; + } + + for(const auto & zone : zonesVector) + { + int level = levels[zone.first]; + totalSize[level] += (zone.second->getSize() * zone.second->getSize()); + float3 center = zone.second->getCenter(); + center.z = level; + zone.second->setCenter(center); + } + + /* + prescale zones + + formula: sum((prescaler*n)^2)*pi = WH + + prescaler = sqrt((WH)/(sum(n^2)*pi)) + */ + + std::vector prescaler = { 0, 0 }; + for (int i = 0; i < 2; i++) + prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); + mapSize = static_cast(sqrt(width * height)); + for(const auto & zone : zones) + { + zone.second->setSize(static_cast(zone.second->getSize() * prescaler[zone.second->getCenter().z])); + } +} + +void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const +{ + for(const auto & zone : zones) + { + float3 forceVector(0, 0, 0); + float3 pos = zone.second->getCenter(); + float totalDistance = 0; + + for (const auto & connection : zone.second->getConnections()) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections + continue; + } + + auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; + float3 otherZoneCenter = otherZone->getCenter(); + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + + forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value + + //Attract zone centers always + + float minDistance = 0; + + if (pos.z != otherZoneCenter.z) + minDistance = 0; //zones on different levels can overlap completely + else + minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates + + if (distance > minDistance) + totalDistance += (distance - minDistance); + } + distances[zone.second] = totalDistance; + forceVector.z = 0; //operator - doesn't preserve z coordinate :/ + forces[zone.second] = forceVector; + } +} + +void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps) +{ + for(const auto & zone : zones) + { + float3 forceVector(0, 0, 0); + float3 pos = zone.second->getCenter(); + + float overlap = 0; + //separate overlapping zones + for(const auto & otherZone : zones) + { + float3 otherZoneCenter = otherZone.second->getCenter(); + //zones on different levels don't push away + if (zone == otherZone || pos.z != otherZoneCenter.z) + continue; + + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; + if (distance < minDistance) + { + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; + //negative value + localForce *= scaleForceBetweenZones(zone.second, otherZone.second); + forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); + overlap += (minDistance - distance); //overlapping of small zones hurts us more + } + } + + //move zones away from boundaries + //do not scale boundary distance - zones tend to get squashed + float size = zone.second->getSize() / mapSize; + + auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y) + { + float3 boundary = float3(x, y, pos.z); + auto distance = static_cast(pos.dist2d(boundary)); + overlap += std::max(0, distance - size); //check if we're closer to map boundary than value of zone size + forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value + }; + if (pos.x < size) + { + pushAwayFromBoundary(0, pos.y); + } + if (pos.x > 1 - size) + { + pushAwayFromBoundary(1, pos.y); + } + if (pos.y < size) + { + pushAwayFromBoundary(pos.x, 0); + } + if (pos.y > 1 - size) + { + pushAwayFromBoundary(pos.x, 1); + } + + //Always move repulsive zones away, no matter their distance + //TODO: Consider z plane? + for (auto& connection : zone.second->getConnections()) + { + if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) + { + auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; + float3 otherZoneCenter = otherZone->getCenter(); + + //TODO: Roll into lambda? + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; + localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]); + forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone); + } + } + + overlaps[zone.second] = overlap; + forceVector.z = 0; //operator - doesn't preserve z coordinate :/ + forces[zone.second] = forceVector; + } +} + +void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps) +{ + //The more zones, the greater total distance expected + //Also, higher stiffness make expected movement lower + const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness); + + typedef std::pair> Misplacement; + std::vector misplacedZones; + + float totalDistance = 0; + float totalOverlap = 0; + for (const auto& zone : distances) //find most misplaced zone + { + if (vstd::contains(lastSwappedZones, zone.first->getId())) + { + continue; + } + totalDistance += zone.second; + float overlap = overlaps[zone.first]; + totalOverlap += overlap; + //if distance to actual movement is long, the zone is misplaced + float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); + if (ratio > maxDistanceMovementRatio) + { + misplacedZones.emplace_back(std::make_pair(ratio, zone.first)); + } + } + + if (misplacedZones.empty()) + return; + + boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs) + { + return lhs.first > rhs.first; //Largest displacement first + }); + +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); +#endif + + if (misplacedZones.size() >= 2) + { + //Swap 2 misplaced zones + + auto firstZone = misplacedZones.front().second; + std::shared_ptr secondZone; + std::set connectedZones; + for (const auto& connection : firstZone->getConnections()) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections + continue; + } + connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); + } + + auto level = firstZone->getCenter().z; + for (size_t i = 1; i < misplacedZones.size(); i++) + { + //Only swap zones on the same level + //Don't swap zones that should be connected (Jebus) + + if (misplacedZones[i].second->getCenter().z == level && + !vstd::contains(connectedZones, misplacedZones[i].second->getId())) + { + secondZone = misplacedZones[i].second; + break; + } + } + if (secondZone) + { +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); +#endif + + auto firstCenter = firstZone->getCenter(); + auto secondCenter = secondZone->getCenter(); + firstZone->setCenter(secondCenter); + secondZone->setCenter(firstCenter); + + lastSwappedZones.insert(firstZone->getId()); + lastSwappedZones.insert(secondZone->getId()); + return; + } + } + lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next + + //find most distant zone that should be attracted and move inside it + std::shared_ptr targetZone; + auto misplacedZone = misplacedZones.front().second; + float3 ourCenter = misplacedZone->getCenter(); + + if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1))) + { + //Move one zone towards most distant zone to reduce distance + + float maxDistance = 0; + for (auto con : misplacedZone->getConnections()) + { + if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE) + { + continue; + } + + auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())]; + float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); + if (distance > maxDistance) + { + maxDistance = distance; + targetZone = otherZone; + } + } + if (targetZone) + { + float3 vec = targetZone->getCenter() - ourCenter; + float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif + + misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size + } + } + else + { + //Move misplaced zone away from overlapping zone + + float maxOverlap = 0; + for(const auto & otherZone : zones) + { + float3 otherZoneCenter = otherZone.second->getCenter(); + + if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) + continue; + + auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); + if (distance > maxOverlap) + { + maxOverlap = distance; + targetZone = otherZone.second; + } + } + if (targetZone) + { + float3 vec = ourCenter - targetZone->getCenter(); + float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif + + misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated + } + } + //Don't swap that zone in next iteration + lastSwappedZones.insert(misplacedZone->getId()); +} + +float CZonePlacer::metric (const int3 &A, const int3 &B) const +{ + return A.dist2dSQ(B); + +} + +void CZonePlacer::assignZones(vstd::RNG * rand) +{ + logGlobal->info("Starting zone colouring"); + + auto width = map.getMapGenOptions().getWidth(); + auto height = map.getMapGenOptions().getHeight(); + + + auto zones = map.getZones(); + vstd::erase_if(zones, [](const std::pair> & pr) + { + return pr.second->getType() == ETemplateZoneType::WATER; + }); + + using Dpair = std::pair, float>; + std::vector distances; + distances.reserve(zones.size()); + + //now place zones correctly and assign tiles to each zone + + auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool + { + //bigger zones have smaller distance + return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); + }; + + auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool + { + //bigger zones have smaller distance + return lhs.second < rhs.second; + }; + + auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr & zone) -> void + { + int3 total(0, 0, 0); + auto tiles = zone->area()->getTiles(); + for(const auto & tile : tiles) + { + total += tile; + } + int size = static_cast(tiles.size()); + assert(size); + auto newPos = int3(total.x / size, total.y / size, total.z / size); + zone->setPos(newPos); + zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z)); + }; + + int levels = map.levels(); + + // Find current center of mass for each zone. Move zone to that center to balance zones sizes + std::vector zonesOnLevel; + for(int level = 0; level < levels; level++) + { + zonesOnLevel.push_back(map.getZonesOnLevel(level)); + } + + int3 pos; + + for(pos.z = 0; pos.z < levels; pos.z++) + { + for(pos.x = 0; pos.x < width; pos.x++) + { + for(pos.y = 0; pos.y < height; pos.y++) + { + distances.clear(); + for(const auto & zone : zonesOnLevel[pos.z]) + { + distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); + } + boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone + } + } + } + + for(const auto & zone : zones) + { + if(zone.second->area()->empty()) + throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); + + moveZoneToCenterOfMass(zone.second); + } + + for(const auto & zone : zones) + zone.second->clearTiles(); //now populate them again + + PenroseTiling penrose; + for (int level = 0; level < levels; level++) + { + //Create different tiling for each level + + auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand); + + // Assign zones to closest Penrose vertex + std::map, std::set> vertexMapping; + + for (const auto & vertex : vertices) + { + distances.clear(); + for(const auto & zone : zonesOnLevel[level]) + { + distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); + } + auto closestZone = boost::min_element(distances, compareByDistance)->first; + + vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone + } + + //Assign actual tiles to each zone + pos.z = level; + for (pos.x = 0; pos.x < width; pos.x++) + { + for (pos.y = 0; pos.y < height; pos.y++) + { + distances.clear(); + for(const auto & zoneVertex : vertexMapping) + { + auto zone = zoneVertex.first; + for (const auto & vertex : zoneVertex.second) + { + distances.emplace_back(zone, metric(pos, vertex)); + } + } + + //Tile closest to vertex belongs to zone + auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; + closestZone->area()->add(pos); + map.setZoneID(pos, closestZone->getId()); + } + } + + for(const auto & zone : zonesOnLevel[level]) + { + if(zone.second->area()->empty()) + { + // FIXME: Some vertices are duplicated, but it's not a source of problem + logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); + for (const auto & vertex : vertices) + { + logGlobal->warn("Penrose Vertex: %s", vertex.toString()); + } + throw rmgException("Empty zone after Penrose tiling"); + } + } + } + + //set position (town position) to center of mass of irregular zone + for(const auto & zone : zones) + { + moveZoneToCenterOfMass(zone.second); + + //TODO: similar for islands + #define CREATE_FULL_UNDERGROUND true //consider linking this with water amount + if (zone.second->isUnderground()) + { + if (!CREATE_FULL_UNDERGROUND) + { + auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f); + for(const auto & t : discardTiles) + zone.second->area()->erase(t); + } + + //make sure that terrain inside zone is not a rock + + auto v = zone.second->area()->getTilesVector(); + map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN); + } + } + logGlobal->info("Finished zone colouring"); +} + +const TDistanceMap& CZonePlacer::getDistanceMap() +{ + return distancesBetweenZones; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/modificators/ObstaclePlacer.cpp b/lib/rmg/modificators/ObstaclePlacer.cpp index 5447a256e..22c308daa 100644 --- a/lib/rmg/modificators/ObstaclePlacer.cpp +++ b/lib/rmg/modificators/ObstaclePlacer.cpp @@ -172,4 +172,4 @@ bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const return false; } -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 25c232ca5..323bbf5ab 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -13,6 +13,7 @@ #include "SerializerReflection.h" #include "ESerializationVersion.h" #include "../mapObjects/CGHeroInstance.h" +#include "../battle/BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN @@ -442,6 +443,19 @@ public: load(data[key]); } } + + //void load(BattleHexArray & data) + //{ + // uint32_t length = readAndCheckLength(); + // data.clear(); + // BattleHex hex; + // for(uint32_t i = 0; i < length; i++) + // { + // load(hex); + // data.insert(hex); + // } + //} + void load(std::string &data) { if (hasFeature(Version::COMPACT_STRING_SERIALIZATION)) diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 738cf17c1..6f2936239 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -15,6 +15,7 @@ #include "ESerializationVersion.h" #include "Serializeable.h" #include "../mapObjects/CArmedInstance.h" +#include "../battle/BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 81a7f1a1b..6e8fb9b5f 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -508,11 +508,11 @@ bool BattleSpellMechanics::counteringSelector(const Bonus * bonus) const return false; } -std::set BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) const +BattleHexArray BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) const { using namespace SRSLPraserHelpers; - std::set ret; + BattleHexArray ret; std::vector rng = owner->getLevelInfo(getRangeLevel()).range; for(auto & elem : rng) @@ -604,13 +604,13 @@ std::vector BattleSpellMechanics::getPossibleDestinations(size_t in if(fast) { auto stacks = battle()->battleGetAllStacks(); - std::set hexesToCheck; + BattleHexArray hexesToCheck; for(auto stack : stacks) { hexesToCheck.insert(stack->getPosition()); - for(auto adjacent : stack->getPosition().neighbouringTiles()) + for(auto adjacent : BattleHexArray::generateNeighbouringTiles(stack->getPosition())) hexesToCheck.insert(adjacent); } @@ -661,17 +661,17 @@ bool BattleSpellMechanics::isReceptive(const battle::Unit * target) const return targetCondition->isReceptive(this, target); } -std::vector BattleSpellMechanics::rangeInHexes(BattleHex centralHex) const +BattleHexArray BattleSpellMechanics::rangeInHexes(BattleHex centralHex) const { if(isMassive() || !centralHex.isValid()) - return std::vector(1, BattleHex::INVALID); + return BattleHexArray(); Target aimPoint; aimPoint.push_back(Destination(centralHex)); Target spellTarget = transformSpellTarget(aimPoint); - std::set effectRange; + BattleHexArray effectRange; effects->forEachEffect(getEffectLevel(), [&](const effects::Effect * effect, bool & stop) { @@ -681,12 +681,7 @@ std::vector BattleSpellMechanics::rangeInHexes(BattleHex centralHex) } }); - std::vector ret; - ret.reserve(effectRange.size()); - - std::copy(effectRange.begin(), effectRange.end(), std::back_inserter(ret)); - - return ret; + return effectRange; } const Spell * BattleSpellMechanics::getSpell() const diff --git a/lib/spells/BattleSpellMechanics.h b/lib/spells/BattleSpellMechanics.h index a910154d4..2b4655cd3 100644 --- a/lib/spells/BattleSpellMechanics.h +++ b/lib/spells/BattleSpellMechanics.h @@ -1,86 +1,86 @@ -/* - * BattleSpellMechanics.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "ISpellMechanics.h" - -#include "effects/Effects.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleSpellCast; - -namespace spells -{ - -class BattleSpellMechanics : public BaseMechanics -{ -public: - BattleSpellMechanics(const IBattleCast * event, std::shared_ptr effects_, std::shared_ptr targetCondition_); - virtual ~BattleSpellMechanics(); - - // TODO: ??? (what's the difference compared to cast?) - void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override; - - /// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield - bool canBeCast(Problem & problem) const override; - - /// Returns false if spell can not be cast at specified target - bool canBeCastAt(const Target & target, Problem & problem) const override; - - // TODO: ??? (what's the difference compared to applyEffects?) - void cast(ServerCallback * server, const Target & target) override final; - // TODO: ??? (what's the difference compared to cast?) - void castEval(ServerCallback * server, const Target & target) override final; - - /// Returns list of affected stack using currently configured target - std::vector getAffectedStacks(const Target & target) const override final; - - /// Returns list of target types that can be targeted by spell - std::vector getTargetTypes() const override final; - - /// Returns vector of all possible destinations for specified aim type - /// index - ??? - /// current - ??? - std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final; - - /// Returns true if spell can be cast on unit - bool isReceptive(const battle::Unit * target) const override; - - /// Returns list of hexes that are affected by spell assuming cast at centralHex - std::vector rangeInHexes(BattleHex centralHex) const override; - - const Spell * getSpell() const override; - - bool counteringSelector(const Bonus * bonus) const; - -private: - std::shared_ptr effects; - std::shared_ptr targetCondition; - - std::vector affectedUnits; - effects::Effects::EffectsToApply effectsToApply; - - void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); - - std::set collectTargets() const; - - void doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector); - - std::set spellRangeInHexes(BattleHex centralHex) const; - - Target transformSpellTarget(const Target & aimPoint) const; -}; - -} - - -VCMI_LIB_NAMESPACE_END +/* + * BattleSpellMechanics.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "ISpellMechanics.h" + +#include "effects/Effects.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleSpellCast; + +namespace spells +{ + +class BattleSpellMechanics : public BaseMechanics +{ +public: + BattleSpellMechanics(const IBattleCast * event, std::shared_ptr effects_, std::shared_ptr targetCondition_); + virtual ~BattleSpellMechanics(); + + // TODO: ??? (what's the difference compared to cast?) + void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override; + + /// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield + bool canBeCast(Problem & problem) const override; + + /// Returns false if spell can not be cast at specified target + bool canBeCastAt(const Target & target, Problem & problem) const override; + + // TODO: ??? (what's the difference compared to applyEffects?) + void cast(ServerCallback * server, const Target & target) override final; + // TODO: ??? (what's the difference compared to cast?) + void castEval(ServerCallback * server, const Target & target) override final; + + /// Returns list of affected stack using currently configured target + std::vector getAffectedStacks(const Target & target) const override final; + + /// Returns list of target types that can be targeted by spell + std::vector getTargetTypes() const override final; + + /// Returns vector of all possible destinations for specified aim type + /// index - ??? + /// current - ??? + std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final; + + /// Returns true if spell can be cast on unit + bool isReceptive(const battle::Unit * target) const override; + + /// Returns list of hexes that are affected by spell assuming cast at centralHex + BattleHexArray rangeInHexes(BattleHex centralHex) const override; + + const Spell * getSpell() const override; + + bool counteringSelector(const Bonus * bonus) const; + +private: + std::shared_ptr effects; + std::shared_ptr targetCondition; + + std::vector affectedUnits; + effects::Effects::EffectsToApply effectsToApply; + + void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); + + std::set collectTargets() const; + + void doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector); + + BattleHexArray spellRangeInHexes(BattleHex centralHex) const; + + Target transformSpellTarget(const Target & aimPoint) const; +}; + +} + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 79619ac51..09b3aadae 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -357,7 +357,7 @@ int32_t CSpell::getLevelPower(const int32_t skillLevel) const si32 CSpell::getProbability(const FactionID & factionId) const { - if(!vstd::contains(probabilities,factionId)) + if(!vstd::contains(probabilities, factionId)) { return defaultProbability; } @@ -701,7 +701,7 @@ const std::vector & CSpellHandler::getTypeNames() const std::vector CSpellHandler::spellRangeInHexes(std::string input) const { - std::set ret; + BattleHexArray ret; std::string rng = input + ','; //copy + artificial comma for easier handling if(rng.size() >= 2 && std::tolower(rng[0]) != 'x') //there is at least one hex in range (+artificial comma) diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 77e69ff47..24ddb7846 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -17,7 +17,7 @@ #include "../ConstTransitivePtr.h" #include "../int3.h" #include "../GameConstants.h" -#include "../battle/BattleHex.h" +#include "../battle/BattleHexArray.h" #include "../bonuses/Bonus.h" #include "../filesystem/ResourcePath.h" #include "../json/JsonNode.h" diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index 35fedbf72..4fac17a2d 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -1,368 +1,368 @@ -/* - * ISpellMechanics.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include -#include - -#include "../battle/Destination.h" -#include "../int3.h" -#include "../GameConstants.h" -#include "../bonuses/Bonus.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct Query; -class IBattleState; -class CreatureService; -class CMap; -class CGameInfoCallback; -class CBattleInfoCallback; -class JsonNode; -class CStack; -class CGObjectInstance; -class CGHeroInstance; - -namespace spells -{ -class Service; -} - -namespace vstd -{ - class RNG; -} - -#if SCRIPTING_ENABLED -namespace scripting -{ - class Service; -} -#endif - - -///callback to be provided by server -class DLL_LINKAGE SpellCastEnvironment : public ServerCallback -{ -public: - virtual ~SpellCastEnvironment() = default; - - virtual const CMap * getMap() const = 0; - virtual const CGameInfoCallback * getCb() const = 0; - - virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; - virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove - - virtual void genericQuery(Query * request, PlayerColor color, std::function)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented -}; - -namespace spells -{ - -class DLL_LINKAGE IBattleCast -{ -public: - using Value = int32_t; - using Value64 = int64_t; - - using OptionalValue = std::optional; - using OptionalValue64 = std::optional; - - virtual const CSpell * getSpell() const = 0; - virtual Mode getMode() const = 0; - virtual const Caster * getCaster() const = 0; - virtual const CBattleInfoCallback * getBattle() const = 0; - - virtual OptionalValue getSpellLevel() const = 0; - - virtual OptionalValue getEffectPower() const = 0; - virtual OptionalValue getEffectDuration() const = 0; - - virtual OptionalValue64 getEffectValue() const = 0; - - virtual boost::logic::tribool isSmart() const = 0; - virtual boost::logic::tribool isMassive() const = 0; -}; - -///all parameters of particular cast event -class DLL_LINKAGE BattleCast : public IBattleCast -{ -public: - boost::logic::tribool smart; - boost::logic::tribool massive; - - //normal constructor - BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_); - - //magic mirror constructor - BattleCast(const BattleCast & orig, const Caster * caster_); - - virtual ~BattleCast(); - - ///IBattleCast - const CSpell * getSpell() const override; - Mode getMode() const override; - const Caster * getCaster() const override; - const CBattleInfoCallback * getBattle() const override; - - OptionalValue getSpellLevel() const override; - - OptionalValue getEffectPower() const override; - OptionalValue getEffectDuration() const override; - - OptionalValue64 getEffectValue() const override; - - boost::logic::tribool isSmart() const override; - boost::logic::tribool isMassive() const override; - - void setSpellLevel(Value value); - - void setEffectPower(Value value); - void setEffectDuration(Value value); - - void setEffectValue(Value64 value); - - ///only apply effects to specified targets - void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const; - - ///normal cast - void cast(ServerCallback * server, Target target); - - ///cast evaluation - void castEval(ServerCallback * server, Target target); - - ///cast with silent check for permitted cast - bool castIfPossible(ServerCallback * server, Target target); - - std::vector findPotentialTargets(bool fast = false) const; - -private: - ///spell school level - OptionalValue magicSkillLevel; - - ///actual spell-power affecting effect values - OptionalValue effectPower; - ///actual spell-power affecting effect duration - OptionalValue effectDuration; - - ///for Archangel-like casting - OptionalValue64 effectValue; - - Mode mode; - const CSpell * spell; - const CBattleInfoCallback * cb; - const Caster * caster; -}; - -class DLL_LINKAGE ISpellMechanicsFactory -{ -public: - virtual ~ISpellMechanicsFactory(); - - virtual std::unique_ptr create(const IBattleCast * event) const = 0; - - static std::unique_ptr get(const CSpell * s); - -protected: - const CSpell * spell; - - ISpellMechanicsFactory(const CSpell * s); -}; - -class DLL_LINKAGE Mechanics -{ -public: - virtual ~Mechanics(); - - virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0; - virtual bool adaptGenericProblem(Problem & target) const = 0; - - virtual std::vector rangeInHexes(BattleHex centralHex) const = 0; - virtual std::vector getAffectedStacks(const Target & target) const = 0; - - virtual bool canBeCast(Problem & problem) const = 0; - virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0; - - virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0; - - virtual void cast(ServerCallback * server, const Target & target) = 0; - - virtual void castEval(ServerCallback * server, const Target & target) = 0; - - virtual bool isReceptive(const battle::Unit * target) const = 0; - - virtual std::vector getTargetTypes() const = 0; - - virtual std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0; - - virtual const Spell * getSpell() const = 0; - - //Cast event facade - - virtual IBattleCast::Value getEffectLevel() const = 0; - virtual IBattleCast::Value getRangeLevel() const = 0; - - virtual IBattleCast::Value getEffectPower() const = 0; - virtual IBattleCast::Value getEffectDuration() const = 0; - - virtual IBattleCast::Value64 getEffectValue() const = 0; - - virtual PlayerColor getCasterColor() const = 0; - - //Spell facade - virtual int32_t getSpellIndex() const = 0; - virtual SpellID getSpellId() const = 0; - virtual std::string getSpellName() const = 0; - virtual int32_t getSpellLevel() const = 0; - - virtual bool isSmart() const = 0; - virtual bool isMassive() const = 0; - virtual bool alwaysHitFirstTarget() const = 0; - virtual bool requiresClearTiles() const = 0; - - virtual bool isNegativeSpell() const = 0; - virtual bool isPositiveSpell() const = 0; - virtual bool isMagicalEffect() const = 0; - - virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0; - virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0; - virtual int64_t applySpecificSpellBonus(int64_t value) const = 0; - virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0; - - //Battle facade - virtual bool ownerMatches(const battle::Unit * unit) const = 0; - virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0; - - //Global environment facade - virtual const CreatureService * creatures() const = 0; -#if SCRIPTING_ENABLED - virtual const scripting::Service * scripts() const = 0; -#endif - virtual const Service * spells() const = 0; - - virtual const CBattleInfoCallback * battle() const = 0; - - const Caster * caster; - - BattleSide casterSide; - -protected: - Mechanics(); -}; - -class DLL_LINKAGE BaseMechanics : public Mechanics -{ -public: - virtual ~BaseMechanics(); - - bool adaptProblem(ESpellCastProblem source, Problem & target) const override; - bool adaptGenericProblem(Problem & target) const override; - - int32_t getSpellIndex() const override; - SpellID getSpellId() const override; - std::string getSpellName() const override; - int32_t getSpellLevel() const override; - - IBattleCast::Value getEffectLevel() const override; - IBattleCast::Value getRangeLevel() const override; - - IBattleCast::Value getEffectPower() const override; - IBattleCast::Value getEffectDuration() const override; - - IBattleCast::Value64 getEffectValue() const override; - - PlayerColor getCasterColor() const override; - - bool isSmart() const override; - bool isMassive() const override; - bool requiresClearTiles() const override; - bool alwaysHitFirstTarget() const override; - - bool isNegativeSpell() const override; - bool isPositiveSpell() const override; - bool isMagicalEffect() const override; - - int64_t adjustEffectValue(const battle::Unit * target) const override; - int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override; - int64_t applySpecificSpellBonus(int64_t value) const override; - int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override; - - bool ownerMatches(const battle::Unit * unit) const override; - bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override; - - std::vector getTargetTypes() const override; - - const CreatureService * creatures() const override; -#if SCRIPTING_ENABLED - const scripting::Service * scripts() const override; -#endif - const Service * spells() const override; - - const CBattleInfoCallback * battle() const override; - -protected: - const CSpell * owner; - Mode mode; - - BaseMechanics(const IBattleCast * event); - -private: - IBattleCast::Value rangeLevel; - IBattleCast::Value effectLevel; - - ///actual spell-power affecting effect values - IBattleCast::Value effectPower; - ///actual spell-power affecting effect duration - IBattleCast::Value effectDuration; - - ///raw damage/heal amount - IBattleCast::Value64 effectValue; - - boost::logic::tribool smart; - boost::logic::tribool massive; - - const CBattleInfoCallback * cb; -}; - -class DLL_LINKAGE IReceptiveCheck -{ -public: - virtual ~IReceptiveCheck() = default; - - virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0; -}; - -}// namespace spells - -class DLL_LINKAGE AdventureSpellCastParameters -{ -public: - const spells::Caster * caster; - int3 pos; -}; - -class DLL_LINKAGE IAdventureSpellMechanics -{ -public: - IAdventureSpellMechanics(const CSpell * s); - virtual ~IAdventureSpellMechanics() = default; - - virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0; - virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0; - - virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0; - - static std::unique_ptr createMechanics(const CSpell * s); -protected: - const CSpell * owner; -}; - -VCMI_LIB_NAMESPACE_END +/* + * ISpellMechanics.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include +#include + +#include "../battle/Destination.h" +#include "../int3.h" +#include "../GameConstants.h" +#include "../bonuses/Bonus.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct Query; +class IBattleState; +class CreatureService; +class CMap; +class CGameInfoCallback; +class CBattleInfoCallback; +class JsonNode; +class CStack; +class CGObjectInstance; +class CGHeroInstance; + +namespace spells +{ +class Service; +} + +namespace vstd +{ + class RNG; +} + +#if SCRIPTING_ENABLED +namespace scripting +{ + class Service; +} +#endif + + +///callback to be provided by server +class DLL_LINKAGE SpellCastEnvironment : public ServerCallback +{ +public: + virtual ~SpellCastEnvironment() = default; + + virtual const CMap * getMap() const = 0; + virtual const CGameInfoCallback * getCb() const = 0; + + virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; + virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove + + virtual void genericQuery(Query * request, PlayerColor color, std::function)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented +}; + +namespace spells +{ + +class DLL_LINKAGE IBattleCast +{ +public: + using Value = int32_t; + using Value64 = int64_t; + + using OptionalValue = std::optional; + using OptionalValue64 = std::optional; + + virtual const CSpell * getSpell() const = 0; + virtual Mode getMode() const = 0; + virtual const Caster * getCaster() const = 0; + virtual const CBattleInfoCallback * getBattle() const = 0; + + virtual OptionalValue getSpellLevel() const = 0; + + virtual OptionalValue getEffectPower() const = 0; + virtual OptionalValue getEffectDuration() const = 0; + + virtual OptionalValue64 getEffectValue() const = 0; + + virtual boost::logic::tribool isSmart() const = 0; + virtual boost::logic::tribool isMassive() const = 0; +}; + +///all parameters of particular cast event +class DLL_LINKAGE BattleCast : public IBattleCast +{ +public: + boost::logic::tribool smart; + boost::logic::tribool massive; + + //normal constructor + BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_); + + //magic mirror constructor + BattleCast(const BattleCast & orig, const Caster * caster_); + + virtual ~BattleCast(); + + ///IBattleCast + const CSpell * getSpell() const override; + Mode getMode() const override; + const Caster * getCaster() const override; + const CBattleInfoCallback * getBattle() const override; + + OptionalValue getSpellLevel() const override; + + OptionalValue getEffectPower() const override; + OptionalValue getEffectDuration() const override; + + OptionalValue64 getEffectValue() const override; + + boost::logic::tribool isSmart() const override; + boost::logic::tribool isMassive() const override; + + void setSpellLevel(Value value); + + void setEffectPower(Value value); + void setEffectDuration(Value value); + + void setEffectValue(Value64 value); + + ///only apply effects to specified targets + void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const; + + ///normal cast + void cast(ServerCallback * server, Target target); + + ///cast evaluation + void castEval(ServerCallback * server, Target target); + + ///cast with silent check for permitted cast + bool castIfPossible(ServerCallback * server, Target target); + + std::vector findPotentialTargets(bool fast = false) const; + +private: + ///spell school level + OptionalValue magicSkillLevel; + + ///actual spell-power affecting effect values + OptionalValue effectPower; + ///actual spell-power affecting effect duration + OptionalValue effectDuration; + + ///for Archangel-like casting + OptionalValue64 effectValue; + + Mode mode; + const CSpell * spell; + const CBattleInfoCallback * cb; + const Caster * caster; +}; + +class DLL_LINKAGE ISpellMechanicsFactory +{ +public: + virtual ~ISpellMechanicsFactory(); + + virtual std::unique_ptr create(const IBattleCast * event) const = 0; + + static std::unique_ptr get(const CSpell * s); + +protected: + const CSpell * spell; + + ISpellMechanicsFactory(const CSpell * s); +}; + +class DLL_LINKAGE Mechanics +{ +public: + virtual ~Mechanics(); + + virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0; + virtual bool adaptGenericProblem(Problem & target) const = 0; + + virtual BattleHexArray rangeInHexes(BattleHex centralHex) const = 0; + virtual std::vector getAffectedStacks(const Target & target) const = 0; + + virtual bool canBeCast(Problem & problem) const = 0; + virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0; + + virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0; + + virtual void cast(ServerCallback * server, const Target & target) = 0; + + virtual void castEval(ServerCallback * server, const Target & target) = 0; + + virtual bool isReceptive(const battle::Unit * target) const = 0; + + virtual std::vector getTargetTypes() const = 0; + + virtual std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0; + + virtual const Spell * getSpell() const = 0; + + //Cast event facade + + virtual IBattleCast::Value getEffectLevel() const = 0; + virtual IBattleCast::Value getRangeLevel() const = 0; + + virtual IBattleCast::Value getEffectPower() const = 0; + virtual IBattleCast::Value getEffectDuration() const = 0; + + virtual IBattleCast::Value64 getEffectValue() const = 0; + + virtual PlayerColor getCasterColor() const = 0; + + //Spell facade + virtual int32_t getSpellIndex() const = 0; + virtual SpellID getSpellId() const = 0; + virtual std::string getSpellName() const = 0; + virtual int32_t getSpellLevel() const = 0; + + virtual bool isSmart() const = 0; + virtual bool isMassive() const = 0; + virtual bool alwaysHitFirstTarget() const = 0; + virtual bool requiresClearTiles() const = 0; + + virtual bool isNegativeSpell() const = 0; + virtual bool isPositiveSpell() const = 0; + virtual bool isMagicalEffect() const = 0; + + virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0; + virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0; + virtual int64_t applySpecificSpellBonus(int64_t value) const = 0; + virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0; + + //Battle facade + virtual bool ownerMatches(const battle::Unit * unit) const = 0; + virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0; + + //Global environment facade + virtual const CreatureService * creatures() const = 0; +#if SCRIPTING_ENABLED + virtual const scripting::Service * scripts() const = 0; +#endif + virtual const Service * spells() const = 0; + + virtual const CBattleInfoCallback * battle() const = 0; + + const Caster * caster; + + BattleSide casterSide; + +protected: + Mechanics(); +}; + +class DLL_LINKAGE BaseMechanics : public Mechanics +{ +public: + virtual ~BaseMechanics(); + + bool adaptProblem(ESpellCastProblem source, Problem & target) const override; + bool adaptGenericProblem(Problem & target) const override; + + int32_t getSpellIndex() const override; + SpellID getSpellId() const override; + std::string getSpellName() const override; + int32_t getSpellLevel() const override; + + IBattleCast::Value getEffectLevel() const override; + IBattleCast::Value getRangeLevel() const override; + + IBattleCast::Value getEffectPower() const override; + IBattleCast::Value getEffectDuration() const override; + + IBattleCast::Value64 getEffectValue() const override; + + PlayerColor getCasterColor() const override; + + bool isSmart() const override; + bool isMassive() const override; + bool requiresClearTiles() const override; + bool alwaysHitFirstTarget() const override; + + bool isNegativeSpell() const override; + bool isPositiveSpell() const override; + bool isMagicalEffect() const override; + + int64_t adjustEffectValue(const battle::Unit * target) const override; + int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override; + int64_t applySpecificSpellBonus(int64_t value) const override; + int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override; + + bool ownerMatches(const battle::Unit * unit) const override; + bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override; + + std::vector getTargetTypes() const override; + + const CreatureService * creatures() const override; +#if SCRIPTING_ENABLED + const scripting::Service * scripts() const override; +#endif + const Service * spells() const override; + + const CBattleInfoCallback * battle() const override; + +protected: + const CSpell * owner; + Mode mode; + + BaseMechanics(const IBattleCast * event); + +private: + IBattleCast::Value rangeLevel; + IBattleCast::Value effectLevel; + + ///actual spell-power affecting effect values + IBattleCast::Value effectPower; + ///actual spell-power affecting effect duration + IBattleCast::Value effectDuration; + + ///raw damage/heal amount + IBattleCast::Value64 effectValue; + + boost::logic::tribool smart; + boost::logic::tribool massive; + + const CBattleInfoCallback * cb; +}; + +class DLL_LINKAGE IReceptiveCheck +{ +public: + virtual ~IReceptiveCheck() = default; + + virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0; +}; + +}// namespace spells + +class DLL_LINKAGE AdventureSpellCastParameters +{ +public: + const spells::Caster * caster; + int3 pos; +}; + +class DLL_LINKAGE IAdventureSpellMechanics +{ +public: + IAdventureSpellMechanics(const CSpell * s); + virtual ~IAdventureSpellMechanics() = default; + + virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0; + virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0; + + virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0; + + static std::unique_ptr createMechanics(const CSpell * s); +protected: + const CSpell * owner; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Effect.h b/lib/spells/effects/Effect.h index d70b6ec7a..f20af46c3 100644 --- a/lib/spells/effects/Effect.h +++ b/lib/spells/effects/Effect.h @@ -1,82 +1,83 @@ -/* - * Effect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleHex; -class CBattleInfoCallback; -class JsonSerializeFormat; -class ServerCallback; - -namespace vstd -{ - class RNG; -} - -namespace spells -{ -using EffectTarget = Target; - -namespace effects -{ -using RNG = vstd::RNG; -class Effects; -class Effect; -class Registry; - -using TargetType = spells::AimType; - -class DLL_LINKAGE Effect -{ -public: - bool indirect = false; - bool optional = false; - - std::string name; - - virtual ~Effect() = default; //Required for child classes - - // TODO: document me - virtual void adjustTargetTypes(std::vector & types) const = 0; - - /// Generates list of hexes affected by spell, if spell were to cast at specified target - virtual void adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const = 0; - - /// Returns whether effect has any valid targets on the battlefield - virtual bool applicable(Problem & problem, const Mechanics * m) const; - - /// Returns whether effect is valid and can be applied onto selected target - virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const; - - virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0; - - /// Processes input target and generates subset-result that contains only valid targets - virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0; - - // TODO: document me - virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0; - - /// Serializes (or deserializes) parameters of Effect - void serializeJson(JsonSerializeFormat & handler); - - static std::shared_ptr create(const Registry * registry, const std::string & type); - -protected: - virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Effect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleHex; +class BattleHexArray; +class CBattleInfoCallback; +class JsonSerializeFormat; +class ServerCallback; + +namespace vstd +{ + class RNG; +} + +namespace spells +{ +using EffectTarget = Target; + +namespace effects +{ +using RNG = vstd::RNG; +class Effects; +class Effect; +class Registry; + +using TargetType = spells::AimType; + +class DLL_LINKAGE Effect +{ +public: + bool indirect = false; + bool optional = false; + + std::string name; + + virtual ~Effect() = default; //Required for child classes + + // TODO: document me + virtual void adjustTargetTypes(std::vector & types) const = 0; + + /// Generates list of hexes affected by spell, if spell were to cast at specified target + virtual void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const = 0; + + /// Returns whether effect has any valid targets on the battlefield + virtual bool applicable(Problem & problem, const Mechanics * m) const; + + /// Returns whether effect is valid and can be applied onto selected target + virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const; + + virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0; + + /// Processes input target and generates subset-result that contains only valid targets + virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0; + + // TODO: document me + virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0; + + /// Serializes (or deserializes) parameters of Effect + void serializeJson(JsonSerializeFormat & handler); + + static std::shared_ptr create(const Registry * registry, const std::string & type); + +protected: + virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/LocationEffect.cpp b/lib/spells/effects/LocationEffect.cpp index e1cefc3c0..82ed0f54c 100644 --- a/lib/spells/effects/LocationEffect.cpp +++ b/lib/spells/effects/LocationEffect.cpp @@ -1,52 +1,52 @@ -/* - * LocationEffect.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include "LocationEffect.h" -#include "../ISpellMechanics.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -void LocationEffect::adjustTargetTypes(std::vector & types) const -{ - -} - -void LocationEffect::adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const -{ - for(const auto & destnation : spellTarget) - hexes.insert(destnation.hexValue); -} - -EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const -{ - EffectTarget res; - vstd::copy_if(target, std::back_inserter(res), [](const Destination & d) - { - return !d.unitValue && (d.hexValue.isValid()); - }); - return res; -} - -EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const -{ - //by default effect covers exactly spell range - return EffectTarget(spellTarget); -} - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * LocationEffect.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "LocationEffect.h" +#include "../ISpellMechanics.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +void LocationEffect::adjustTargetTypes(std::vector & types) const +{ + +} + +void LocationEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const +{ + for(const auto & destnation : spellTarget) + hexes.insert(destnation.hexValue); +} + +EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const +{ + EffectTarget res; + vstd::copy_if(target, std::back_inserter(res), [](const Destination & d) + { + return !d.unitValue && (d.hexValue.isValid()); + }); + return res; +} + +EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const +{ + //by default effect covers exactly spell range + return EffectTarget(spellTarget); +} + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/LocationEffect.h b/lib/spells/effects/LocationEffect.h index 1ce98dbff..a47c7e608 100644 --- a/lib/spells/effects/LocationEffect.h +++ b/lib/spells/effects/LocationEffect.h @@ -1,41 +1,41 @@ -/* - * LocationEffect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" - -VCMI_LIB_NAMESPACE_BEGIN - - -namespace spells -{ -namespace effects -{ - -class LocationEffect : public Effect -{ -public: - void adjustTargetTypes(std::vector & types) const override; - - void adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; -protected: - -private: -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * LocationEffect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" + +VCMI_LIB_NAMESPACE_BEGIN + + +namespace spells +{ +namespace effects +{ + +class LocationEffect : public Effect +{ +public: + void adjustTargetTypes(std::vector & types) const override; + + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; +protected: + +private: +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index 889644b88..e32db09a0 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -32,7 +32,7 @@ namespace spells namespace effects { -static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string & fieldName, std::vector> & moatHexes) +static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string & fieldName, std::vector & moatHexes) { { JsonArraySerializer outer = handler.enterArray(fieldName); @@ -43,8 +43,12 @@ static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string JsonArraySerializer inner = outer.enterArray(outerIndex); inner.syncSize(moatHexes.at(outerIndex), JsonNode::JsonType::DATA_INTEGER); + BattleHex hex; for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++) - inner.serializeInt(innerIndex, moatHexes.at(outerIndex).at(innerIndex)); + { + inner.serializeInt(innerIndex, hex); + moatHexes.at(outerIndex).set(innerIndex, hex); + } } } } @@ -96,10 +100,10 @@ void Moat::convertBonus(const Mechanics * m, std::vector & converted) con nb.sid = BonusSourceID(m->getSpellId()); //for all nb.source = BonusSource::SPELL_EFFECT;//for all } - std::set flatMoatHexes; + BattleHexArray flatMoatHexes; for(const auto & moatPatch : moatHexes) - flatMoatHexes.insert(moatPatch.begin(), moatPatch.end()); + flatMoatHexes.merge(moatPatch); nb.limiter = std::make_shared(std::move(flatMoatHexes)); converted.push_back(nb); @@ -164,7 +168,7 @@ void Moat::placeObstacles(ServerCallback * server, const Mechanics * m, const Ef obstacle.appearSound = sideOptions.appearSound; //For dispellable moats obstacle.appearAnimation = sideOptions.appearAnimation; //For dispellable moats obstacle.animation = sideOptions.animation; - obstacle.customSize.insert(obstacle.customSize.end(),destination.cbegin(), destination.cend()); + obstacle.customSize.merge(destination); obstacle.animationYOffset = sideOptions.offsetY; pack.changes.emplace_back(); obstacle.toInfo(pack.changes.back()); diff --git a/lib/spells/effects/Moat.h b/lib/spells/effects/Moat.h index c30130743..4e0c020f8 100644 --- a/lib/spells/effects/Moat.h +++ b/lib/spells/effects/Moat.h @@ -1,43 +1,43 @@ -/* - * Moat.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Obstacle.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct Bonus; - -namespace spells -{ -namespace effects -{ - -class Moat : public Obstacle -{ -private: - ObstacleSideOptions sideOptions; //Defender only - std::vector> moatHexes; //Determine number of moat patches and hexes - std::vector> bonus; //For battle-wide bonuses - bool dispellable; //For Tower landmines - int moatDamage; // Minimal moat damage -public: - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override; - void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - void convertBonus(const Mechanics * m, std::vector & converted) const; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Moat.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Obstacle.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct Bonus; + +namespace spells +{ +namespace effects +{ + +class Moat : public Obstacle +{ +private: + ObstacleSideOptions sideOptions; //Defender only + std::vector moatHexes; //Determine number of moat patches and hexes + std::vector> bonus; //For battle-wide bonuses + bool dispellable; //For Tower landmines + int moatDamage; // Minimal moat damage +public: + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override; + void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + void convertBonus(const Mechanics * m, std::vector & converted) const; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Obstacle.cpp b/lib/spells/effects/Obstacle.cpp index 40c22a088..027d7041c 100644 --- a/lib/spells/effects/Obstacle.cpp +++ b/lib/spells/effects/Obstacle.cpp @@ -95,7 +95,7 @@ void ObstacleSideOptions::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("offsetY", offsetY); } -void Obstacle::adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const +void Obstacle::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const { EffectTarget effectTarget = transformTarget(m, spellTarget, spellTarget); @@ -180,11 +180,11 @@ void Obstacle::apply(ServerCallback * server, const Mechanics * m, const EffectT { if(patchCount > 0) { - std::vector availableTiles; - auto insertAvailable = [&m](const BattleHex & hex, std::vector & availableTiles) + BattleHexArray availableTiles; + auto insertAvailable = [&m](const BattleHex & hex, BattleHexArray & availableTiles) { if(isHexAvailable(m->battle(), hex, true)) - availableTiles.push_back(hex); + availableTiles.insert(hex); }; if(m->isMassive()) @@ -309,7 +309,6 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons obstacle.animationYOffset = options.offsetY; obstacle.customSize.clear(); - obstacle.customSize.reserve(options.shape.size()); for(const auto & shape : options.shape) { @@ -318,7 +317,7 @@ void Obstacle::placeObstacles(ServerCallback * server, const Mechanics * m, cons for(auto direction : shape) hex.moveInDirection(direction, false); - obstacle.customSize.emplace_back(hex); + obstacle.customSize.insert(hex); } pack.changes.emplace_back(); diff --git a/lib/spells/effects/Obstacle.h b/lib/spells/effects/Obstacle.h index b6410aed4..7677d4523 100644 --- a/lib/spells/effects/Obstacle.h +++ b/lib/spells/effects/Obstacle.h @@ -1,78 +1,78 @@ -/* - * Obstacle.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "LocationEffect.h" -#include "../../GameConstants.h" -#include "../../battle/BattleHex.h" -#include "../../battle/CObstacleInstance.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class ObstacleSideOptions -{ -public: - using RelativeShape = std::vector>; - - RelativeShape shape; //shape of single obstacle relative to obstacle position - RelativeShape range; //position of obstacles relative to effect destination - - AudioPath appearSound; - AnimationPath appearAnimation; - AnimationPath animation; - - int offsetY = 0; - - void serializeJson(JsonSerializeFormat & handler); -}; - -class Obstacle : public LocationEffect -{ -public: - void adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override; - virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const; - - bool hidden = false; - bool trigger = false; - bool trap = false; - bool removeOnTrigger = false; - bool hideNative = false; - SpellID triggerAbility; -private: - int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine) - bool passable = false; - int32_t turnsRemaining = -1; - - BattleSideArray sideOptions; - - static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear); - static bool noRoomToPlace(Problem & problem, const Mechanics * m); -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Obstacle.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "LocationEffect.h" +#include "../../GameConstants.h" +#include "../../battle/BattleHexArray.h" +#include "../../battle/CObstacleInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class ObstacleSideOptions +{ +public: + using RelativeShape = std::vector>; + + RelativeShape shape; //shape of single obstacle relative to obstacle position + RelativeShape range; //position of obstacles relative to effect destination + + AudioPath appearSound; + AnimationPath appearAnimation; + AnimationPath animation; + + int offsetY = 0; + + void serializeJson(JsonSerializeFormat & handler); +}; + +class Obstacle : public LocationEffect +{ +public: + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override; + virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const; + + bool hidden = false; + bool trigger = false; + bool trap = false; + bool removeOnTrigger = false; + bool hideNative = false; + SpellID triggerAbility; +private: + int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine) + bool passable = false; + int32_t turnsRemaining = -1; + + BattleSideArray sideOptions; + + static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear); + static bool noRoomToPlace(Problem & problem, const Mechanics * m); +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Summon.cpp b/lib/spells/effects/Summon.cpp index e4d51bd5b..160f1020d 100644 --- a/lib/spells/effects/Summon.cpp +++ b/lib/spells/effects/Summon.cpp @@ -28,7 +28,7 @@ namespace spells namespace effects { -void Summon::adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const +void Summon::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const { //no hexes affected } @@ -207,4 +207,4 @@ EffectTarget Summon::transformTarget(const Mechanics * m, const Target & aimPoin } } -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/spells/effects/Summon.h b/lib/spells/effects/Summon.h index 017f7fe2e..55b09fc47 100644 --- a/lib/spells/effects/Summon.h +++ b/lib/spells/effects/Summon.h @@ -1,55 +1,55 @@ -/* - * Summon.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" -#include "../../GameConstants.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class Summon : public Effect -{ -public: - void adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const override; - void adjustTargetTypes(std::vector & types) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override final; - -private: - int32_t summonedCreatureAmount(const Mechanics * m) const; - int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const; - - CreatureID creature; - - bool permanent = false; - bool exclusive = true; - bool summonByHealth = false; - bool summonSameUnit = false; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Summon.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" +#include "../../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class Summon : public Effect +{ +public: + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + void adjustTargetTypes(std::vector & types) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override final; + +private: + int32_t summonedCreatureAmount(const Mechanics * m) const; + int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const; + + CreatureID creature; + + bool permanent = false; + bool exclusive = true; + bool summonByHealth = false; + bool summonSameUnit = false; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Teleport.cpp b/lib/spells/effects/Teleport.cpp index cffe3c2f1..f7139fff2 100644 --- a/lib/spells/effects/Teleport.cpp +++ b/lib/spells/effects/Teleport.cpp @@ -81,8 +81,8 @@ void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectT pack.battleID = m->battle()->getBattle()->getBattleID(); pack.distance = 0; pack.stack = targetUnit->unitId(); - std::vector tiles; - tiles.push_back(destination); + BattleHexArray tiles; + tiles.insert(destination); pack.tilesToMove = tiles; pack.teleporting = true; server->apply(pack); diff --git a/lib/spells/effects/UnitEffect.cpp b/lib/spells/effects/UnitEffect.cpp index 3598fbbb8..9a1f028e2 100644 --- a/lib/spells/effects/UnitEffect.cpp +++ b/lib/spells/effects/UnitEffect.cpp @@ -30,7 +30,7 @@ void UnitEffect::adjustTargetTypes(std::vector & types) const } -void UnitEffect::adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const +void UnitEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const { for(const auto & destnation : spellTarget) hexes.insert(destnation.hexValue); @@ -193,7 +193,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe return EffectTarget(); } - std::set possibleHexes; + BattleHexArray possibleHexes; auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool { @@ -228,7 +228,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe if(possibleHexes.empty()) break; - destHex = BattleHex::getClosestTile(unit->unitSide(), destHex, possibleHexes); + destHex = possibleHexes.getClosestTile(unit->unitSide(), destHex); } return effectTarget; @@ -278,4 +278,4 @@ void UnitEffect::serializeJsonEffect(JsonSerializeFormat & handler) } } -VCMI_LIB_NAMESPACE_END +VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/spells/effects/UnitEffect.h b/lib/spells/effects/UnitEffect.h index e17d2f7e9..521e5ab0a 100644 --- a/lib/spells/effects/UnitEffect.h +++ b/lib/spells/effects/UnitEffect.h @@ -1,61 +1,61 @@ -/* - * UnitEffect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class UnitEffect : public Effect -{ -public: - void adjustTargetTypes(std::vector & types) const override; - - void adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - - bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const; - - virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const; - -protected: - int32_t chainLength = 0; - double chainFactor = 0.0; - - virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const; - virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const; - virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const; - - void serializeJsonEffect(JsonSerializeFormat & handler) override final; - virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0; - -private: - bool ignoreImmunity = false; - - EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; - EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * UnitEffect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class UnitEffect : public Effect +{ +public: + void adjustTargetTypes(std::vector & types) const override; + + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + + bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const; + + virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const; + +protected: + int32_t chainLength = 0; + double chainFactor = 0.0; + + virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const; + virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const; + virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const; + + void serializeJsonEffect(JsonSerializeFormat & handler) override final; + virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0; + +private: + bool ignoreImmunity = false; + + EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; + EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/server/battles/BattleActionProcessor.cpp b/server/battles/BattleActionProcessor.cpp index cdfed2975..1ba3a3cdc 100644 --- a/server/battles/BattleActionProcessor.cpp +++ b/server/battles/BattleActionProcessor.cpp @@ -635,7 +635,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta //initing necessary tables auto accessibility = battle.getAccessibility(curStack); - std::set passed; + BattleHexArray passed; //Ignore obstacles on starting position passed.insert(curStack->getPosition()); if(curStack->doubleWide()) @@ -665,7 +665,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta canUseGate = true; } - std::pair< std::vector, int > path = battle.getPath(start, dest, curStack); + std::pair< BattleHexArray, int > path = battle.getPath(start, dest, curStack); ret = path.second; @@ -723,8 +723,8 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta BattleStackMoved sm; sm.battleID = battle.getBattle()->getBattleID(); sm.stack = curStack->unitId(); - std::vector tiles; - tiles.push_back(path.first[0]); + BattleHexArray tiles; + tiles.insert(path.first[0]); sm.tilesToMove = tiles; sm.distance = path.second; sm.teleporting = false; @@ -733,10 +733,10 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta } else //for non-flying creatures { - std::vector tiles; + BattleHexArray tiles; const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0); int v = (int)path.first.size()-1; - path.first.push_back(start); + path.first.insert(start); // check if gate need to be open or closed at some point BattleHex openGateAtHex, gateMayCloseAtHex; @@ -822,7 +822,7 @@ int BattleActionProcessor::moveStack(const CBattleInfoCallback & battle, int sta for (bool obstacleHit = false; (!obstacleHit) && (!gateStateChanging) && (v >= tilesToMove); --v) { BattleHex hex = path.first[v]; - tiles.push_back(hex); + tiles.insert(hex); if ((openGateAtHex.isValid() && openGateAtHex == hex) || (gateMayCloseAtHex.isValid() && gateMayCloseAtHex == hex)) diff --git a/server/battles/BattleFlowProcessor.cpp b/server/battles/BattleFlowProcessor.cpp index 058f84f20..18712bebd 100644 --- a/server/battles/BattleFlowProcessor.cpp +++ b/server/battles/BattleFlowProcessor.cpp @@ -36,7 +36,7 @@ BattleFlowProcessor::BattleFlowProcessor(BattleProcessor * owner, CGameHandler * { } -void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & battle, std::vector & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard +void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard { int x = targetPosition.getX(); int y = targetPosition.getY(); @@ -44,31 +44,31 @@ void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & batt const bool targetIsAttacker = side == BattleSide::ATTACKER; if (targetIsAttacker) //handle front guardians, TODO: should we handle situation when units start battle near opposite side of the battlefield? Cannot happen in normal H3... - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false)); else - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false)); //guardian spawn locations for four default position cases for attacker and defender, non-default starting location for att and def is handled in first two if's if (targetIsAttacker && ((y % 2 == 0) || (x > 1))) { if (targetIsTwoHex && (y % 2 == 1) && (x == 2)) //handle exceptional case { - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_RIGHT, false)); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false)); } else { //add back-side guardians for two-hex target, side guardians for one-hex - BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_LEFT : BattleHex::EDir::TOP_RIGHT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_LEFT : BattleHex::EDir::BOTTOM_RIGHT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_LEFT : BattleHex::EDir::TOP_RIGHT, false)); + output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_LEFT : BattleHex::EDir::BOTTOM_RIGHT, false)); if (!targetIsTwoHex && x > 2) //back guard for one-hex - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false)); else if (targetIsTwoHex)//front-side guardians for two-hex target { - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false)); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false)); if (x > 3) //back guard for two-hex - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::LEFT, false)); } } @@ -78,36 +78,36 @@ void BattleFlowProcessor::summonGuardiansHelper(const CBattleInfoCallback & batt { if (targetIsTwoHex && (y % 2 == 0) && (x == GameConstants::BFIELD_WIDTH - 3)) //handle exceptional case... equivalent for above for defender side { - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); } else { - BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_RIGHT : BattleHex::EDir::TOP_LEFT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_RIGHT : BattleHex::EDir::BOTTOM_LEFT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::TOP_RIGHT : BattleHex::EDir::TOP_LEFT, false)); + output.checkAndPush(targetPosition.cloneInDirection(targetIsTwoHex ? BattleHex::EDir::BOTTOM_RIGHT : BattleHex::EDir::BOTTOM_LEFT, false)); if (!targetIsTwoHex && x < GameConstants::BFIELD_WIDTH - 3) - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false)); else if (targetIsTwoHex) { - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); if (x < GameConstants::BFIELD_WIDTH - 4) - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::RIGHT, false)); } } } else if (!targetIsAttacker && y % 2 == 0) { - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::LEFT, false).cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); } else if (targetIsAttacker && y % 2 == 1) { - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false), output); - BattleHex::checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false), output); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::TOP_RIGHT, false)); + output.checkAndPush(targetPosition.cloneInDirection(BattleHex::EDir::RIGHT, false).cloneInDirection(BattleHex::EDir::BOTTOM_RIGHT, false)); } } @@ -150,7 +150,7 @@ void BattleFlowProcessor::trySummonGuardians(const CBattleInfoCallback & battle, std::shared_ptr summonInfo = stack->getBonus(Selector::type()(BonusType::SUMMON_GUARDIANS)); auto accessibility = battle.getAccessibility(); CreatureID creatureData = summonInfo->subtype.as(); - std::vector targetHexes; + BattleHexArray targetHexes; const bool targetIsBig = stack->unitType()->isDoubleWide(); //target = creature to guard const bool guardianIsBig = creatureData.toCreature()->isDoubleWide(); diff --git a/server/battles/BattleFlowProcessor.h b/server/battles/BattleFlowProcessor.h index 70ef29249..6982f739d 100644 --- a/server/battles/BattleFlowProcessor.h +++ b/server/battles/BattleFlowProcessor.h @@ -1,61 +1,62 @@ -/* - * BattleFlowProcessor.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../lib/battle/BattleSide.h" - -VCMI_LIB_NAMESPACE_BEGIN -class CStack; -struct BattleHex; -class BattleAction; -class CBattleInfoCallback; -struct CObstacleInstance; -namespace battle -{ -class Unit; -} -VCMI_LIB_NAMESPACE_END - -class CGameHandler; -class BattleProcessor; - -/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions -class BattleFlowProcessor : boost::noncopyable -{ - BattleProcessor * owner; - CGameHandler * gameHandler; - - const CStack * getNextStack(const CBattleInfoCallback & battle); - - bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack); - bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack); - - void summonGuardiansHelper(const CBattleInfoCallback & battle, std::vector & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex); - void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack); - void tryPlaceMoats(const CBattleInfoCallback & battle); - void castOpeningSpells(const CBattleInfoCallback & battle); - void activateNextStack(const CBattleInfoCallback & battle); - void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound); - - void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack); - void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle); - void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack); - void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack); - - void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next); - bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) - -public: - explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler); - - void onBattleStarted(const CBattleInfoCallback & battle); - void onTacticsEnded(const CBattleInfoCallback & battle); - void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba); -}; +/* + * BattleFlowProcessor.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../lib/battle/BattleSide.h" + +VCMI_LIB_NAMESPACE_BEGIN +class CStack; +struct BattleHex; +class BattleHexArray; +class BattleAction; +class CBattleInfoCallback; +struct CObstacleInstance; +namespace battle +{ +class Unit; +} +VCMI_LIB_NAMESPACE_END + +class CGameHandler; +class BattleProcessor; + +/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions +class BattleFlowProcessor : boost::noncopyable +{ + BattleProcessor * owner; + CGameHandler * gameHandler; + + const CStack * getNextStack(const CBattleInfoCallback & battle); + + bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack); + bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack); + + void summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex); + void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack); + void tryPlaceMoats(const CBattleInfoCallback & battle); + void castOpeningSpells(const CBattleInfoCallback & battle); + void activateNextStack(const CBattleInfoCallback & battle); + void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound); + + void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack); + void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle); + void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack); + void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack); + + void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next); + bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) + +public: + explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler); + + void onBattleStarted(const CBattleInfoCallback & battle); + void onTacticsEnded(const CBattleInfoCallback & battle); + void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba); +}; From 5f799d41b31cdf1f5174bb376836486b0fbe1458 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Mon, 4 Nov 2024 13:27:19 +0100 Subject: [PATCH 03/14] Use cached neighbouring tiles where possible --- AI/BattleAI/BattleExchangeVariant.cpp | 5 +- AI/StupidAI/StupidAI.cpp | 2 +- lib/battle/BattleHexArray.cpp | 89 ++++++++++++++++++++++----- lib/battle/BattleHexArray.h | 35 ++++++----- lib/battle/CBattleInfoCallback.cpp | 8 +-- lib/battle/ReachabilityInfo.cpp | 2 +- lib/battle/Unit.cpp | 4 +- lib/spells/BattleSpellMechanics.cpp | 2 +- 8 files changed, 104 insertions(+), 43 deletions(-) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 2aba3c771..69bbb1a5f 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -959,8 +959,7 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) { - BattleHexArray neighbours; - neighbours.generateNeighbouringTiles(hex); + const BattleHexArray & neighbours = BattleHexArray::neighbouringTilesCache[hex.hex]; for(BattleHex neighbour : neighbours) { reachable = unitReachability.distances.at(neighbour) <= radius; @@ -1022,7 +1021,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) { enemyUnit = true; - for(BattleHex neighbour : BattleHexArray::generateNeighbouringTiles(hex)) + for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[hex.hex]) { reachable = unitReachability.distances.at(neighbour) <= unitSpeed; diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 79c28f41c..8ddab9b5c 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -107,7 +107,7 @@ static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr for(int i = 0; i < 2; i++) { - for (auto neighbour : BattleHexArray::generateNeighbouringTiles(i ? h2 : h1)) + for (auto neighbour : BattleHexArray::neighbouringTilesCache[i ? h2.hex : h1.hex]) if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour)) if(s->isShooter()) shooters[i]++; diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index ddfa23609..261a2ef64 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -22,7 +22,7 @@ BattleHexArray::BattleHexArray(std::initializer_list initList) noexce } } -BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) +BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) const { BattleHex initialHex = BattleHex(initialPos); auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool @@ -54,8 +54,76 @@ BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) }; boost::sort(sortedTiles, compareHorizontal); return sortedTiles.front(); +} + +BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTiles() +{ + BattleHexArray::NeighbouringTilesCache ret; + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + BattleHexArray hexes = BattleHexArray::generateNeighbouringTiles(hex); + + size_t index = 0; + ret[hex].resize(hexes.size()); + for(auto neighbour : hexes) + ret[hex].set(index++, neighbour); + } + + return ret; +} + +BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) +{ + BattleHexArray ret; + for(auto dir : BattleHex::hexagonalDirections()) + ret.checkAndPush(hex.cloneInDirection(dir, false)); + + return ret; +} + +BattleHexArray BattleHexArray::generateAttackerClosestTilesCache() +{ + assert(!neighbouringTilesCache.empty()); + + BattleHexArray ret; + + ret.resize(GameConstants::BFIELD_SIZE); + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); + } + + return ret; +} + +BattleHexArray BattleHexArray::generateDefenderClosestTilesCache() +{ + assert(!neighbouringTilesCache.empty()); + + BattleHexArray ret; + + ret.resize(GameConstants::BFIELD_SIZE); + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::DEFENDER, hex)); + } + + return ret; } +BattleHex BattleHexArray::getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos) +{ + if(side == BattleSide::ATTACKER) + return closestTilesCacheForAttacker[pos.hex]; + else if(side == BattleSide::DEFENDER) + return closestTilesCacheForDefender[pos.hex]; + else + assert(false); // we should never be here +} + void BattleHexArray::merge(const BattleHexArray & other) noexcept { for(auto hex : other) @@ -82,22 +150,9 @@ void BattleHexArray::clear() noexcept internalStorage.clear(); } -static BattleHexArray::NeighbouringTilesCache calculateNeighbouringTiles() -{ - BattleHexArray::NeighbouringTilesCache ret; - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - auto hexes = BattleHexArray::generateNeighbouringTiles(hex); - - size_t index = 0; - for(auto neighbour : hexes) - ret[hex].at(index++) = neighbour; - } - - return ret; -} - const BattleHexArray::NeighbouringTilesCache BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles(); +const BattleHexArray BattleHexArray::closestTilesCacheForAttacker = generateAttackerClosestTilesCache(); +const BattleHexArray BattleHexArray::closestTilesCacheForDefender = generateDefenderClosestTilesCache(); + VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 81eb217c7..4c9810328 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -33,10 +33,11 @@ public: using reverse_iterator = typename StorageType::reverse_iterator; using const_reverse_iterator = typename StorageType::const_reverse_iterator; - using NeighbouringTiles = std::array; - using NeighbouringTilesCache = std::array; + using NeighbouringTilesCache = std::array; static const NeighbouringTilesCache neighbouringTilesCache; + static const BattleHexArray closestTilesCacheForAttacker; + static const BattleHexArray closestTilesCacheForDefender; BattleHexArray() noexcept { @@ -62,16 +63,6 @@ public: BattleHexArray(std::initializer_list initList) noexcept; - /// returns all valid neighbouring tiles - static BattleHexArray generateNeighbouringTiles(BattleHex hex) - { - BattleHexArray ret; - for(auto dir : BattleHex::hexagonalDirections()) - ret.checkAndPush(hex.cloneInDirection(dir, false)); - - return ret; - } - /// returns all tiles, unavailable tiles will be set as invalid /// order of returned tiles matches EDir enum static BattleHexArray generateAllNeighbouringTiles(BattleHex hex) @@ -86,8 +77,6 @@ public: return ret; } - BattleHex getClosestTile(BattleSide side, BattleHex initialPos); - void checkAndPush(BattleHex tile) { if(tile.isAvailable() && !contains(tile)) @@ -114,6 +103,14 @@ public: /*if(isNotValidForInsertion(hex)) return;*/ + if(index >= internalStorage.size()) + { + logGlobal->error("Invalid BattleHexArray::set index parameter. It is " + std::to_string(index) + + " and current size is " + std::to_string(internalStorage.size())); + throw std::out_of_range("Invalid BattleHexArray::set index parameter. It is " + std::to_string(index) + + " and current size is " + std::to_string(internalStorage.size())); + } + if(contains(hex)) return; @@ -133,6 +130,10 @@ public: return internalStorage.insert(pos, hex); } + static BattleHex getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos); + + BattleHex getClosestTile(BattleSide side, BattleHex initialPos) const; + void merge(const BattleHexArray & other) noexcept; void clear() noexcept; @@ -295,6 +296,12 @@ private: { return hex == BattleHex::CASTLE_CENTRAL_TOWER || hex == BattleHex::CASTLE_UPPER_TOWER || hex == BattleHex::CASTLE_BOTTOM_TOWER; } + + /// returns all valid neighbouring tiles + static BattleHexArray::NeighbouringTilesCache calculateNeighbouringTiles(); + static BattleHexArray generateNeighbouringTiles(BattleHex hex); + static BattleHexArray generateAttackerClosestTilesCache(); + static BattleHexArray generateDefenderClosestTilesCache(); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 3d09b5138..ef567ccf4 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -204,7 +204,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, while (next != dest) { - next = BattleHexArray::generateNeighbouringTiles(next).getClosestTile(direction, dest); + next = BattleHexArray::neighbouringTilesCache[next].getClosestTile(direction, dest); ret.insert(next); } assert(!ret.empty()); @@ -1360,7 +1360,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( } if(attacker->hasBonusOfType(BonusType::WIDE_BREATH)) { - BattleHexArray hexes = BattleHexArray::generateNeighbouringTiles(destinationTile); + BattleHexArray hexes = BattleHexArray::neighbouringTilesCache[destinationTile]; for(int i = 0; i < hexes.size(); i++) { if(hexes.at(i) == attackOriginHex) @@ -1433,9 +1433,9 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle:: AttackableTiles at; RETURN_IF_NOT_BATTLE(at); - if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::generateNeighbouringTiles(attackerPos).contains(destinationTile)) + if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::neighbouringTilesCache[attackerPos].contains(destinationTile)) { - auto targetHexes = BattleHexArray::generateNeighbouringTiles(destinationTile); + auto targetHexes = BattleHexArray::neighbouringTilesCache[destinationTile]; targetHexes.insert(destinationTile); boost::copy(targetHexes, vstd::set_inserter(at.hostileCreaturePositions)); } diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index a96dc3fd6..826fa6626 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -43,7 +43,7 @@ uint32_t ReachabilityInfo::distToNearestNeighbour( for(auto targetHex : targetHexes) { - for(auto & n : BattleHexArray::generateNeighbouringTiles(targetHex)) + for(auto & n : BattleHexArray::neighbouringTilesCache[targetHex]) { if(distances[n] < ret) { diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index 2e74a9133..a8b438ced 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -88,7 +88,7 @@ BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, Battle } else { - return BattleHexArray::generateNeighbouringTiles(position); + return BattleHexArray::neighbouringTilesCache[position]; } } @@ -112,7 +112,7 @@ BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const hexes.pop_back(); for(auto hex : hexes) - targetableHexes.merge(BattleHexArray::generateNeighbouringTiles(hex)); + targetableHexes.merge(BattleHexArray::neighbouringTilesCache[hex]); } return targetableHexes; diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 6e8fb9b5f..5a66b4331 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -610,7 +610,7 @@ std::vector BattleSpellMechanics::getPossibleDestinations(size_t in { hexesToCheck.insert(stack->getPosition()); - for(auto adjacent : BattleHexArray::generateNeighbouringTiles(stack->getPosition())) + for(auto adjacent : BattleHexArray::neighbouringTilesCache[stack->getPosition().hex]) hexesToCheck.insert(adjacent); } From 78845706c91452ac226ea807dff3d8ecc482b3c0 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Sun, 10 Nov 2024 21:04:14 +0100 Subject: [PATCH 04/14] GetClosestTile refactor --- lib/battle/BattleHexArray.cpp | 174 +++++++----- lib/battle/Unit.cpp | 483 +++++++++++++++++----------------- 2 files changed, 346 insertions(+), 311 deletions(-) diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index 261a2ef64..e36e98e96 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -24,39 +24,71 @@ BattleHexArray::BattleHexArray(std::initializer_list initList) noexce BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) const { + if(this->empty()) + return BattleHex(); + BattleHex initialHex = BattleHex(initialPos); - auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool + int closestDistance = std::numeric_limits::max(); + BattleHexArray closestTiles; + + for(auto hex : internalStorage) { - return initialHex.getDistance(initialHex, left) < initialHex.getDistance(initialHex, right); - }; - BattleHexArray sortedTiles(*this); - boost::sort(sortedTiles, compareDistance); //closest tiles at front - int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away - auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool - { - return closestDistance < here.getDistance(initialPos, here); - }; - vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting - auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool + int distance = initialHex.getDistance(initialHex, hex); + if(distance < closestDistance) + { + closestDistance = distance; + closestTiles.clear(); + closestTiles.insert(hex); + } + else if(distance == closestDistance) + closestTiles.insert(hex); + } + + auto compareHorizontal = [side, initialPos](const BattleHex & left, const BattleHex & right) { if(left.getX() != right.getX()) { - if(side == BattleSide::ATTACKER) - return left.getX() > right.getX(); //find furthest right - else - return left.getX() < right.getX(); //find furthest left - } - else - { - //Prefer tiles in the same row. - return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); + return (side == BattleSide::ATTACKER) ? (left.getX() > right.getX()) : (left.getX() < right.getX()); } + return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); }; - boost::sort(sortedTiles, compareHorizontal); - return sortedTiles.front(); -} - -BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTiles() + + auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal); + return (bestTile != closestTiles.end()) ? *bestTile : BattleHex(); + + //BattleHex initialHex = BattleHex(initialPos); + //auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool + //{ + // return initialHex.getDistance(initialHex, left) < initialHex.getDistance(initialHex, right); + //}; + //BattleHexArray sortedTiles(*this); + //boost::sort(sortedTiles, compareDistance); //closest tiles at front + //int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away + //auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool + //{ + // return closestDistance < here.getDistance(initialPos, here); + //}; + //vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting + //auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool + //{ + // if(left.getX() != right.getX()) + // { + // if(side == BattleSide::ATTACKER) + // return left.getX() > right.getX(); //find furthest right + // else + // return left.getX() < right.getX(); //find furthest left + // } + // else + // { + // //Prefer tiles in the same row. + // return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); + // } + //}; + //boost::sort(sortedTiles, compareHorizontal); + //return sortedTiles.front(); +} + +BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTiles() { BattleHexArray::NeighbouringTilesCache ret; @@ -71,59 +103,59 @@ BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTile } return ret; -} - -BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) +} + +BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) { BattleHexArray ret; for(auto dir : BattleHex::hexagonalDirections()) ret.checkAndPush(hex.cloneInDirection(dir, false)); return ret; -} - -BattleHexArray BattleHexArray::generateAttackerClosestTilesCache() -{ - assert(!neighbouringTilesCache.empty()); - - BattleHexArray ret; - - ret.resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); - } - - return ret; -} - -BattleHexArray BattleHexArray::generateDefenderClosestTilesCache() -{ - assert(!neighbouringTilesCache.empty()); - - BattleHexArray ret; - - ret.resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::DEFENDER, hex)); - } - - return ret; } -BattleHex BattleHexArray::getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos) -{ - if(side == BattleSide::ATTACKER) - return closestTilesCacheForAttacker[pos.hex]; - else if(side == BattleSide::DEFENDER) - return closestTilesCacheForDefender[pos.hex]; - else - assert(false); // we should never be here -} - +BattleHexArray BattleHexArray::generateAttackerClosestTilesCache() +{ + assert(!neighbouringTilesCache.empty()); + + BattleHexArray ret; + + ret.resize(GameConstants::BFIELD_SIZE); + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); + } + + return ret; +} + +BattleHexArray BattleHexArray::generateDefenderClosestTilesCache() +{ + assert(!neighbouringTilesCache.empty()); + + BattleHexArray ret; + + ret.resize(GameConstants::BFIELD_SIZE); + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::DEFENDER, hex)); + } + + return ret; +} + +BattleHex BattleHexArray::getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos) +{ + if(side == BattleSide::ATTACKER) + return closestTilesCacheForAttacker[pos.hex]; + else if(side == BattleSide::DEFENDER) + return closestTilesCacheForDefender[pos.hex]; + else + assert(false); // we should never be here +} + void BattleHexArray::merge(const BattleHexArray & other) noexcept { for(auto hex : other) diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index a8b438ced..021173901 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -1,240 +1,243 @@ -/* - * Unit.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include "Unit.h" - -#include "../VCMI_Lib.h" -#include "../texts/CGeneralTextHandler.h" - -#include "../serializer/JsonDeserializer.h" -#include "../serializer/JsonSerializer.h" - -#include -#include - -VCMI_LIB_NAMESPACE_BEGIN - -namespace battle -{ - -///Unit -Unit::~Unit() = default; - -bool Unit::isDead() const -{ - return !alive() && !isGhost(); -} - -bool Unit::isTurret() const -{ - return creatureIndex() == CreatureID::ARROW_TOWERS; -} - -std::string Unit::getDescription() const -{ - boost::format fmt("Unit %d of side %d"); - fmt % unitId() % static_cast(unitSide()); - return fmt.str(); -} - -//TODO: deduplicate these functions -const IBonusBearer* Unit::getBonusBearer() const -{ - return this; -} - -BattleHexArray Unit::getSurroundingHexes(BattleHex assumedPosition) const -{ - BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position - - return getSurroundingHexes(hex, doubleWide(), unitSide()); -} - -BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) -{ - BattleHexArray hexes; - if(twoHex) - { - const BattleHex otherHex = occupiedHex(position, twoHex, side); - - if(side == BattleSide::ATTACKER) - { - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(position.cloneInDirection(dir, false)); - - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - } - else - { - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); - - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false)); - } - return hexes; - } - else - { - return BattleHexArray::neighbouringTilesCache[position]; - } -} - -BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const -{ - auto defenderHexes = battle::Unit::getHexes( - getPosition(), - doubleWide(), - unitSide()); - - BattleHexArray targetableHexes; - - for(auto defenderHex : defenderHexes) - { - auto hexes = battle::Unit::getHexes( - defenderHex, - attacker->doubleWide(), - unitSide()); - - if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1) - hexes.pop_back(); - - for(auto hex : hexes) - targetableHexes.merge(BattleHexArray::neighbouringTilesCache[hex]); - } - - return targetableHexes; -} - -bool Unit::coversPos(BattleHex pos) const -{ - return getPosition() == pos || (doubleWide() && (occupiedHex() == pos)); -} - -BattleHexArray Unit::getHexes() const -{ - return getHexes(getPosition(), doubleWide(), unitSide()); -} - -BattleHexArray Unit::getHexes(BattleHex assumedPos) const -{ - return getHexes(assumedPos, doubleWide(), unitSide()); -} - -BattleHexArray Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) -{ - BattleHexArray hexes; - hexes.insert(assumedPos); - - if(twoHex) - hexes.insert(occupiedHex(assumedPos, twoHex, side)); - - return hexes; -} - -BattleHex Unit::occupiedHex() const -{ - return occupiedHex(getPosition(), doubleWide(), unitSide()); -} - -BattleHex Unit::occupiedHex(BattleHex assumedPos) const -{ - return occupiedHex(assumedPos, doubleWide(), unitSide()); -} - -BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side) -{ - if(twoHex) - { - if(side == BattleSide::ATTACKER) - return assumedPos - 1; - else - return assumedPos + 1; - } - else - { - return BattleHex::INVALID; - } -} - -void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const -{ - if(boost::logic::indeterminate(plural)) - serial = VLC->generaltexth->pluralText(serial, getCount()); - else if(plural) - serial = VLC->generaltexth->pluralText(serial, 2); - else - serial = VLC->generaltexth->pluralText(serial, 1); - - text.appendLocalString(type, serial); -} - -void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const -{ - if(boost::logic::indeterminate(plural)) - text.replaceName(creatureId(), getCount()); - else if(plural) - text.replaceNamePlural(creatureIndex()); - else - text.replaceNameSingular(creatureIndex()); -} - -std::string Unit::formatGeneralMessage(const int32_t baseTextId) const -{ - const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount()); - - MetaString text; - text.appendLocalString(EMetaText::GENERAL_TXT, textId); - text.replaceName(creatureId(), getCount()); - - return text.toString(); -} - -int Unit::getRawSurrenderCost() const -{ - //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines - if(unitSlot().validSlot()) - return creatureCost() * getCount(); - else - return 0; -} - -///UnitInfo -void UnitInfo::serializeJson(JsonSerializeFormat & handler) -{ - handler.serializeInt("count", count); - handler.serializeId("type", type, CreatureID(CreatureID::NONE)); - handler.serializeInt("side", side); - handler.serializeInt("position", position); - handler.serializeBool("summoned", summoned); -} - -void UnitInfo::save(JsonNode & data) -{ - data.clear(); - JsonSerializer ser(nullptr, data); - ser.serializeStruct("newUnitInfo", *this); -} - -void UnitInfo::load(uint32_t id_, const JsonNode & data) -{ - id = id_; - JsonDeserializer deser(nullptr, data); - deser.serializeStruct("newUnitInfo", *this); -} - -} - -VCMI_LIB_NAMESPACE_END +/* + * Unit.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "Unit.h" + +#include "../VCMI_Lib.h" +#include "../texts/CGeneralTextHandler.h" + +#include "../serializer/JsonDeserializer.h" +#include "../serializer/JsonSerializer.h" + +#include +#include + +VCMI_LIB_NAMESPACE_BEGIN + +namespace battle +{ + +///Unit +Unit::~Unit() = default; + +bool Unit::isDead() const +{ + return !alive() && !isGhost(); +} + +bool Unit::isTurret() const +{ + return creatureIndex() == CreatureID::ARROW_TOWERS; +} + +std::string Unit::getDescription() const +{ + boost::format fmt("Unit %d of side %d"); + fmt % unitId() % static_cast(unitSide()); + return fmt.str(); +} + +//TODO: deduplicate these functions +const IBonusBearer* Unit::getBonusBearer() const +{ + return this; +} + +BattleHexArray Unit::getSurroundingHexes(BattleHex assumedPosition) const +{ + BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position + + return getSurroundingHexes(hex, doubleWide(), unitSide()); +} + +BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) +{ + if(!position.isValid()) + return { }; + + BattleHexArray hexes; + if(twoHex) + { + const BattleHex otherHex = occupiedHex(position, twoHex, side); + + if(side == BattleSide::ATTACKER) + { + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(position.cloneInDirection(dir, false)); + + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + } + else + { + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); + + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false)); + } + return hexes; + } + else + { + return BattleHexArray::neighbouringTilesCache[position]; + } +} + +BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const +{ + auto defenderHexes = battle::Unit::getHexes( + getPosition(), + doubleWide(), + unitSide()); + + BattleHexArray targetableHexes; + + for(auto defenderHex : defenderHexes) + { + auto hexes = battle::Unit::getHexes( + defenderHex, + attacker->doubleWide(), + unitSide()); + + if(hexes.size() == 2 && BattleHex::getDistance(hexes.front(), hexes.back()) != 1) + hexes.pop_back(); + + for(auto hex : hexes) + targetableHexes.merge(BattleHexArray::neighbouringTilesCache[hex]); + } + + return targetableHexes; +} + +bool Unit::coversPos(BattleHex pos) const +{ + return getPosition() == pos || (doubleWide() && (occupiedHex() == pos)); +} + +BattleHexArray Unit::getHexes() const +{ + return getHexes(getPosition(), doubleWide(), unitSide()); +} + +BattleHexArray Unit::getHexes(BattleHex assumedPos) const +{ + return getHexes(assumedPos, doubleWide(), unitSide()); +} + +BattleHexArray Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) +{ + BattleHexArray hexes; + hexes.insert(assumedPos); + + if(twoHex) + hexes.insert(occupiedHex(assumedPos, twoHex, side)); + + return hexes; +} + +BattleHex Unit::occupiedHex() const +{ + return occupiedHex(getPosition(), doubleWide(), unitSide()); +} + +BattleHex Unit::occupiedHex(BattleHex assumedPos) const +{ + return occupiedHex(assumedPos, doubleWide(), unitSide()); +} + +BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side) +{ + if(twoHex) + { + if(side == BattleSide::ATTACKER) + return assumedPos - 1; + else + return assumedPos + 1; + } + else + { + return BattleHex::INVALID; + } +} + +void Unit::addText(MetaString & text, EMetaText type, int32_t serial, const boost::logic::tribool & plural) const +{ + if(boost::logic::indeterminate(plural)) + serial = VLC->generaltexth->pluralText(serial, getCount()); + else if(plural) + serial = VLC->generaltexth->pluralText(serial, 2); + else + serial = VLC->generaltexth->pluralText(serial, 1); + + text.appendLocalString(type, serial); +} + +void Unit::addNameReplacement(MetaString & text, const boost::logic::tribool & plural) const +{ + if(boost::logic::indeterminate(plural)) + text.replaceName(creatureId(), getCount()); + else if(plural) + text.replaceNamePlural(creatureIndex()); + else + text.replaceNameSingular(creatureIndex()); +} + +std::string Unit::formatGeneralMessage(const int32_t baseTextId) const +{ + const int32_t textId = VLC->generaltexth->pluralText(baseTextId, getCount()); + + MetaString text; + text.appendLocalString(EMetaText::GENERAL_TXT, textId); + text.replaceName(creatureId(), getCount()); + + return text.toString(); +} + +int Unit::getRawSurrenderCost() const +{ + //we pay for our stack that comes from our army slots - condition eliminates summoned cres and war machines + if(unitSlot().validSlot()) + return creatureCost() * getCount(); + else + return 0; +} + +///UnitInfo +void UnitInfo::serializeJson(JsonSerializeFormat & handler) +{ + handler.serializeInt("count", count); + handler.serializeId("type", type, CreatureID(CreatureID::NONE)); + handler.serializeInt("side", side); + handler.serializeInt("position", position); + handler.serializeBool("summoned", summoned); +} + +void UnitInfo::save(JsonNode & data) +{ + data.clear(); + JsonSerializer ser(nullptr, data); + ser.serializeStruct("newUnitInfo", *this); +} + +void UnitInfo::load(uint32_t id_, const JsonNode & data) +{ + id = id_; + JsonDeserializer deser(nullptr, data); + deser.serializeStruct("newUnitInfo", *this); +} + +} + +VCMI_LIB_NAMESPACE_END From 8c3a4175276fdbd575b86d009253d083ef879dac Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Tue, 12 Nov 2024 07:11:18 +0100 Subject: [PATCH 05/14] Cleanup --- AI/BattleAI/BattleAI.h | 2 +- AI/Nullkiller/Helpers/ExplorationHelper.h | 98 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- .../Pathfinding/ObjectGraphCalculator.cpp | 28 +- .../Pathfinding/ObjectGraphCalculator.h | 112 +- AI/StupidAI/StupidAI.h | 112 +- AI/VCAI/Goals/Explore.cpp | 896 +++---- AI/VCAI/Goals/Explore.h | 132 +- client/battle/BattleFieldController.cpp | 3 + client/battle/BattleFieldController.h | 282 +-- client/battle/BattleInterface.h | 464 ++-- client/battle/BattleStacksController.h | 298 +-- include/vstd/RNG.h | 208 +- lib/BattleFieldHandler.cpp | 202 +- lib/BattleFieldHandler.h | 160 +- lib/ObstacleHandler.cpp | 258 +-- lib/ObstacleHandler.h | 158 +- lib/battle/BattleHex.cpp | 276 +-- lib/battle/BattleHex.h | 254 +- lib/battle/Destination.h | 86 +- lib/battle/IBattleInfoCallback.h | 178 +- lib/battle/ReachabilityInfo.cpp | 178 +- lib/bonuses/Limiters.cpp | 6 +- lib/pathfinder/CGPathNode.h | 486 ++-- lib/rmg/CZonePlacer.cpp | 2054 ++++++++--------- lib/rmg/RmgPath.cpp | 1 + lib/rmg/modificators/ObstaclePlacer.cpp | 2 +- lib/serializer/BinaryDeserializer.h | 14 - lib/serializer/BinarySerializer.h | 1 - lib/spells/BattleSpellMechanics.h | 172 +- lib/spells/ISpellMechanics.h | 736 +++--- lib/spells/effects/Effect.h | 166 +- lib/spells/effects/LocationEffect.cpp | 104 +- lib/spells/effects/LocationEffect.h | 82 +- lib/spells/effects/Moat.h | 86 +- lib/spells/effects/Obstacle.h | 156 +- lib/spells/effects/Summon.h | 110 +- lib/spells/effects/UnitEffect.h | 122 +- server/battles/BattleFlowProcessor.h | 124 +- 39 files changed, 4399 insertions(+), 4410 deletions(-) diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index 18df749f0..4889afe42 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -88,7 +88,7 @@ public: //void battleResultsApplied() override; //called when all effects of last battle are applied //void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; //void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn - //void battleStackMoved(const CStack * stack, std::vector dest, int distance) override; + //void battleStackMoved(const CStack * stack, BattleHexArray dest, int distance) override; //void battleSpellCast(const BattleSpellCast *sc) override; //void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks //void battleTriggerEffect(const BattleTriggerEffect & bte) override; diff --git a/AI/Nullkiller/Helpers/ExplorationHelper.h b/AI/Nullkiller/Helpers/ExplorationHelper.h index 0d58f341d..cd4427de1 100644 --- a/AI/Nullkiller/Helpers/ExplorationHelper.h +++ b/AI/Nullkiller/Helpers/ExplorationHelper.h @@ -1,49 +1,49 @@ -/* -* ExplorationHelper.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#pragma once - -#include "../AIUtility.h" - -#include "../../../lib/GameConstants.h" -#include "../../../lib/VCMI_Lib.h" -#include "../Goals/AbstractGoal.h" - -namespace NKAI -{ - -class ExplorationHelper -{ -private: - const CGHeroInstance * hero; - int sightRadius; - float bestValue; - Goals::TSubgoal bestGoal; - int3 bestTile; - int bestTilesDiscovered; - const Nullkiller * ai; - CCallback * cbp; - const TeamState * ts; - int3 ourPos; - bool allowDeadEndCancellation; - bool useCPathfinderAccessibility; - -public: - ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false); - Goals::TSubgoal makeComposition() const; - bool scanSector(int scanRadius); - bool scanMap(); - int howManyTilesWillBeDiscovered(const int3 & pos) const; - -private: - void scanTile(const int3 & tile); - bool hasReachableneighbour(const int3 & pos) const; -}; - -} +/* +* ExplorationHelper.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "../AIUtility.h" + +#include "../../../lib/GameConstants.h" +#include "../../../lib/VCMI_Lib.h" +#include "../Goals/AbstractGoal.h" + +namespace NKAI +{ + +class ExplorationHelper +{ +private: + const CGHeroInstance * hero; + int sightRadius; + float bestValue; + Goals::TSubgoal bestGoal; + int3 bestTile; + int bestTilesDiscovered; + const Nullkiller * ai; + CCallback * cbp; + const TeamState * ts; + int3 ourPos; + bool allowDeadEndCancellation; + bool useCPathfinderAccessibility; + +public: + ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false); + Goals::TSubgoal makeComposition() const; + bool scanSector(int scanRadius); + bool scanMap(); + int howManyTilesWillBeDiscovered(const int3 & pos) const; + +private: + void scanTile(const int3 & tile); + bool hasReachableNeighbor(const int3 & pos) const; +}; + +} diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index de9ad89b3..a40fbd7d2 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -399,7 +399,7 @@ void AINodeStorage::calculateNeighbours( #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( - "Node %s added to neighbours of %s, layer %d", + "Node %s added to neighbors of %s, layer %d", neighbour.toString(), source.coord.toString(), static_cast(layer)); diff --git a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp index ddaff0e73..8905572c9 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp @@ -56,9 +56,9 @@ void ObjectGraphCalculator::calculateConnections() removeExtraConnections(); } -float ObjectGraphCalculator::getneighbourConnectionsCost(const int3 & pos, std::vector & pathCache) +float ObjectGraphCalculator::getNeighborConnectionsCost(const int3 & pos, std::vector & pathCache) { - float neighbourCost = std::numeric_limits::max(); + float neighborCost = std::numeric_limits::max(); if(NKAI_GRAPH_TRACE_LEVEL >= 2) { @@ -68,24 +68,24 @@ float ObjectGraphCalculator::getneighbourConnectionsCost(const int3 & pos, std:: foreach_neighbour( ai->cb.get(), pos, - [this, &neighbourCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour) + [this, &neighborCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) { - ai->pathfinder->calculatePathInfo(pathCache, neighbour); + ai->pathfinder->calculatePathInfo(pathCache, neighbor); auto costTotal = this->getConnectionsCost(pathCache); - if(costTotal.connectionsCount > 2 && costTotal.avg < neighbourCost) + if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost) { - neighbourCost = costTotal.avg; + neighborCost = costTotal.avg; if(NKAI_GRAPH_TRACE_LEVEL >= 2) { - logAi->trace("Better node found at %s", neighbour.toString()); + logAi->trace("Better node found at %s", neighbor.toString()); } } }); - return neighbourCost; + return neighborCost; } void ObjectGraphCalculator::addMinimalDistanceJunctions() @@ -105,9 +105,9 @@ void ObjectGraphCalculator::addMinimalDistanceJunctions() if(currentCost.connectionsCount <= 2) return; - float neighbourCost = getneighbourConnectionsCost(pos, paths); + float neighborCost = getNeighborConnectionsCost(pos, paths); - if(currentCost.avg < neighbourCost) + if(currentCost.avg < neighborCost) { junctions.insert(pos); } @@ -137,17 +137,17 @@ void ObjectGraphCalculator::calculateConnections(const int3 & pos, std::vectorcb.get(), pos, - [this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour) + [this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) { - if(target->hasNodeAt(neighbour)) + if(target->hasNodeAt(neighbor)) { - ai->pathfinder->calculatePathInfo(pathCache, neighbour); + ai->pathfinder->calculatePathInfo(pathCache, neighbor); for(auto & path : pathCache) { if(pos == path.targetHero->visitablePos()) { - target->tryAddConnection(pos, neighbour, path.movementCost(), path.getTotalDanger()); + target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger()); } } } diff --git a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h index 67b03edff..812bd6985 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h @@ -1,56 +1,56 @@ -/* -* ObjectGraphCalculator.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ - -#pragma once - -#include "ObjectGraph.h" -#include "../AIUtility.h" - -namespace NKAI -{ - -struct ConnectionCostInfo -{ - float totalCost = 0; - float avg = 0; - int connectionsCount = 0; -}; - -class ObjectGraphCalculator -{ -private: - ObjectGraph * target; - const Nullkiller * ai; - std::mutex syncLock; - - std::map actors; - std::map actorObjectMap; - - std::vector> temporaryBoats; - std::vector> temporaryActorHeroes; - -public: - ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai); - void setGraphObjects(); - void calculateConnections(); - float getneighbourConnectionsCost(const int3 & pos, std::vector & pathCache); - void addMinimalDistanceJunctions(); - -private: - void updatePaths(); - void calculateConnections(const int3 & pos, std::vector & pathCache); - bool isExtraConnection(float direct, float side1, float side2) const; - void removeExtraConnections(); - void addObjectActor(const CGObjectInstance * obj); - void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false); - ConnectionCostInfo getConnectionsCost(std::vector & paths) const; -}; - -} +/* +* ObjectGraphCalculator.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "ObjectGraph.h" +#include "../AIUtility.h" + +namespace NKAI +{ + +struct ConnectionCostInfo +{ + float totalCost = 0; + float avg = 0; + int connectionsCount = 0; +}; + +class ObjectGraphCalculator +{ +private: + ObjectGraph * target; + const Nullkiller * ai; + std::mutex syncLock; + + std::map actors; + std::map actorObjectMap; + + std::vector> temporaryBoats; + std::vector> temporaryActorHeroes; + +public: + ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai); + void setGraphObjects(); + void calculateConnections(); + float getNeighborConnectionsCost(const int3 & pos, std::vector & pathCache); + void addMinimalDistanceJunctions(); + +private: + void updatePaths(); + void calculateConnections(const int3 & pos, std::vector & pathCache); + bool isExtraConnection(float direct, float side1, float side2) const; + void removeExtraConnections(); + void addObjectActor(const CGObjectInstance * obj); + void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false); + ConnectionCostInfo getConnectionsCost(std::vector & paths) const; +}; + +} diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index f1fe9171b..06a0e04e8 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -1,56 +1,56 @@ -/* - * StupidAI.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../../lib/battle/BattleHex.h" -#include "../../lib/battle/ReachabilityInfo.h" -#include "../../lib/CGameInterface.h" - -class EnemyInfo; - -class CStupidAI : public CBattleGameInterface -{ - BattleSide side; - std::shared_ptr cb; - std::shared_ptr env; - - bool wasWaitingForRealize; - bool wasUnlockingGs; - - void print(const std::string &text) const; -public: - CStupidAI(); - ~CStupidAI(); - - void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; - void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; - - void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero - void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero - void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack - void yourTacticPhase(const BattleID & battleID, int distance) override; - - void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack - void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) override; //called when stack receives damage (after battleAttack()) - void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; - //void battleResultsApplied() override; //called when all effects of last battle are applied - void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; - void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn - void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; - void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; - void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks - //void battleTriggerEffect(const BattleTriggerEffect & bte) override; - void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right - void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack - -private: - BattleAction goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const; -}; - +/* + * StupidAI.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../../lib/battle/BattleHex.h" +#include "../../lib/battle/ReachabilityInfo.h" +#include "../../lib/CGameInterface.h" + +class EnemyInfo; + +class CStupidAI : public CBattleGameInterface +{ + BattleSide side; + std::shared_ptr cb; + std::shared_ptr env; + + bool wasWaitingForRealize; + bool wasUnlockingGs; + + void print(const std::string &text) const; +public: + CStupidAI(); + ~CStupidAI(); + + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; + + void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero + void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero + void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack + void yourTacticPhase(const BattleID & battleID, int distance) override; + + void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack + void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) override; //called when stack receives damage (after battleAttack()) + void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; + //void battleResultsApplied() override; //called when all effects of last battle are applied + void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; + void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn + void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; + void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; + void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks + //void battleTriggerEffect(const BattleTriggerEffect & bte) override; + void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right + void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack + +private: + BattleAction goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const; +}; + diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp index 55527c056..294103598 100644 --- a/AI/VCAI/Goals/Explore.cpp +++ b/AI/VCAI/Goals/Explore.cpp @@ -1,448 +1,448 @@ -/* -* Explore.cpp, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#include "StdInc.h" -#include "Goals.h" -#include "../VCAI.h" -#include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" -#include "../ResourceManager.h" -#include "../BuildingManager.h" -#include "../../../lib/constants/StringConstants.h" -#include "../../../lib/CPlayerState.h" - -using namespace Goals; - -namespace Goals -{ - struct ExplorationHelper - { - HeroPtr hero; - int sightRadius; - float bestValue; - TSubgoal bestGoal; - VCAI * aip; - CCallback * cbp; - const TeamState * ts; - int3 ourPos; - bool allowDeadEndCancellation; - bool allowGatherArmy; - - ExplorationHelper(HeroPtr h, bool gatherArmy) - { - cbp = cb; - aip = ai; - hero = h; - ts = cbp->getPlayerTeam(ai->playerID); - sightRadius = hero->getSightRadius(); - bestGoal = sptr(Goals::Invalid()); - bestValue = 0; - ourPos = h->visitablePos(); - allowDeadEndCancellation = true; - allowGatherArmy = gatherArmy; - } - - void scanSector(int scanRadius) - { - int3 tile = int3(0, 0, ourPos.z); - - const auto & slice = ts->fogOfWarMap[ourPos.z]; - - for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++) - { - for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++) - { - - if(cbp->isInTheMap(tile) && slice[tile.x][tile.y]) - { - scanTile(tile); - } - } - } - } - - void scanMap() - { - int3 mapSize = cbp->getMapSize(); - int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); - - std::vector from; - std::vector to; - - from.reserve(perimeter); - to.reserve(perimeter); - - foreach_tile_pos([&](const int3 & pos) - { - if(ts->fogOfWarMap[pos.z][pos.x][pos.y]) - { - bool hasInvisibleneighbour = false; - - foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) - { - if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) - { - hasInvisibleneighbour = true; - } - }); - - if(hasInvisibleneighbour) - from.push_back(pos); - } - }); - - logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name); - - for(const int3 & tile : from) - { - scanTile(tile); - } - - if(!bestGoal->invalid()) - { - return; - } - - allowDeadEndCancellation = false; - - for(int i = 0; i < sightRadius; i++) - { - getVisibleNeighbours(from, to); - vstd::concatenate(from, to); - vstd::removeDuplicates(from); - } - - logAi->debug("Exploration scan all possible tiles for hero %s", hero.name); - - for(const int3 & tile : from) - { - scanTile(tile); - } - } - - void scanTile(const int3 & tile) - { - if(tile == ourPos - || !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does - return; - - int tilesDiscovered = howManyTilesWillBeDiscovered(tile); - if(!tilesDiscovered) - return; - - auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy); - for(auto goal : waysToVisit) - { - if(goal->evaluationContext.movementCost <= 0.0) // should not happen - continue; - - float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; - - if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much - { - auto obj = cb->getTopObj(tile); - - // picking up resources does not yield any exploration at all. - // if it blocks the way to some explorable tile AIPathfinder will take care of it - if(obj && obj->isBlockedVisitable()) - { - continue; - } - - if(isSafeToVisit(hero, tile)) - { - bestGoal = goal; - bestValue = ourValue; - } - } - } - } - - void getVisibleNeighbours(const std::vector & tiles, std::vector & out) const - { - for(const int3 & tile : tiles) - { - foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) - { - if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) - { - out.push_back(neighbour); - } - }); - } - } - - int howManyTilesWillBeDiscovered(const int3 & pos) const - { - int ret = 0; - int3 npos = int3(0, 0, pos.z); - - const auto & slice = ts->fogOfWarMap[pos.z]; - - for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++) - { - for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++) - { - if(cbp->isInTheMap(npos) - && pos.dist2d(npos) - 0.5 < sightRadius - && !slice[npos.x][npos.y]) - { - if(allowDeadEndCancellation - && !hasReachableneighbour(npos)) - { - continue; - } - - ret++; - } - } - } - - return ret; - } - - bool hasReachableneighbour(const int3 &pos) const - { - for(crint3 dir : int3::getDirs()) - { - int3 tile = pos + dir; - if(cbp->isInTheMap(tile)) - { - auto isAccessible = aip->ah->isTileAccessible(hero, tile); - - if(isAccessible) - return true; - } - } - - return false; - } - }; -} - -bool Explore::operator==(const Explore & other) const -{ - return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; -} - -std::string Explore::completeMessage() const -{ - return "Hero " + hero.get()->getNameTranslated() + " completed exploration"; -} - -TSubgoal Explore::whatToDoToAchieve() -{ - return fh->chooseSolution(getAllPossibleSubgoals()); -} - -TGoalVec Explore::getAllPossibleSubgoals() -{ - TGoalVec ret; - std::vector heroes; - - if(hero) - { - heroes.push_back(hero.h); - } - else - { - //heroes = ai->getUnblockedHeroes(); - heroes = cb->getHeroesInfo(); - vstd::erase_if(heroes, [](const HeroPtr h) - { - if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer - return true; - - if(!ai->isAbleToExplore(h)) - return true; - - return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway - }); - } - - //try to use buildings that uncover map - std::vector objs; - for(auto obj : ai->visitableObjs) - { - if(!vstd::contains(ai->alreadyVisited, obj)) - { - switch(obj->ID.num) - { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - case Obj::CARTOGRAPHER: - objs.push_back(obj); - break; - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); - if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) - objs.push_back(obj); - break; - } - } - else - { - switch(obj->ID.num) - { - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) - break; - for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) - { - if(!cb->getObj(exit)) - { // Always attempt to visit two-way teleports if one of channel exits is not visible - objs.push_back(obj); - break; - } - } - break; - } - } - } - - for(auto h : heroes) - { - for(auto obj : objs) //double loop, performance risk? - { - auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy); - - vstd::concatenate(ret, waysToVisitObj); - } - - TSubgoal goal = exploreNearestNeighbour(h); - - if(!goal->invalid()) - { - ret.push_back(goal); - } - } - - if(ret.empty()) - { - for(auto h : heroes) - { - logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated()); - - TSubgoal goal = explorationNewPoint(h); - - if(goal->invalid()) - { - ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore - } - else - { - ret.push_back(goal); - } - } - } - - //we either don't have hero yet or none of heroes can explore - if((!hero || ret.empty()) && ai->canRecruitAnyHero()) - ret.push_back(sptr(RecruitHero())); - - if(ret.empty()) - { - throw goalFulfilledException(sptr(Explore().sethero(hero))); - } - - return ret; -} - -bool Explore::fulfillsMe(TSubgoal goal) -{ - if(goal->goalType == EXPLORE) - { - if(goal->hero) - return hero == goal->hero; - else - return true; //cancel ALL exploration - } - return false; -} - -TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const -{ - ExplorationHelper scanResult(h, allowGatherArmy); - - for(crint3 dir : int3::getDirs()) - { - int3 tile = hpos + dir; - if(cb->isInTheMap(tile)) - { - scanResult.scanTile(tile); - } - } - - return scanResult.bestGoal; -} - - -TSubgoal Explore::explorationNewPoint(HeroPtr h) const -{ - ExplorationHelper scanResult(h, allowGatherArmy); - - scanResult.scanSector(10); - - if(!scanResult.bestGoal->invalid()) - { - return scanResult.bestGoal; - } - - scanResult.scanMap(); - - return scanResult.bestGoal; -} - - -TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const -{ - TimeCheck tc("where to explore"); - int3 hpos = h->visitablePos(); - - //look for nearby objs -> visit them if they're close enough - const int DIST_LIMIT = 3; - const float COST_LIMIT = .2f; //todo: fine tune - - std::vector nearbyVisitableObjs; - for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map - { - for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y) - { - for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false)) - { - if(ai->isGoodForVisit(obj, h, COST_LIMIT)) - { - nearbyVisitableObjs.push_back(obj); - } - } - } - } - - if(nearbyVisitableObjs.size()) - { - vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles - boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); - - TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false)); - - if(!pickupNearestObj->invalid()) - { - return pickupNearestObj; - } - } - - //check if nearby tiles allow us to reveal anything - this is quick - return explorationBestNeighbour(hpos, h); -} +/* +* Explore.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/constants/StringConstants.h" +#include "../../../lib/CPlayerState.h" + +using namespace Goals; + +namespace Goals +{ + struct ExplorationHelper + { + HeroPtr hero; + int sightRadius; + float bestValue; + TSubgoal bestGoal; + VCAI * aip; + CCallback * cbp; + const TeamState * ts; + int3 ourPos; + bool allowDeadEndCancellation; + bool allowGatherArmy; + + ExplorationHelper(HeroPtr h, bool gatherArmy) + { + cbp = cb; + aip = ai; + hero = h; + ts = cbp->getPlayerTeam(ai->playerID); + sightRadius = hero->getSightRadius(); + bestGoal = sptr(Goals::Invalid()); + bestValue = 0; + ourPos = h->visitablePos(); + allowDeadEndCancellation = true; + allowGatherArmy = gatherArmy; + } + + void scanSector(int scanRadius) + { + int3 tile = int3(0, 0, ourPos.z); + + const auto & slice = ts->fogOfWarMap[ourPos.z]; + + for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++) + { + for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++) + { + + if(cbp->isInTheMap(tile) && slice[tile.x][tile.y]) + { + scanTile(tile); + } + } + } + } + + void scanMap() + { + int3 mapSize = cbp->getMapSize(); + int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); + + std::vector from; + std::vector to; + + from.reserve(perimeter); + to.reserve(perimeter); + + foreach_tile_pos([&](const int3 & pos) + { + if(ts->fogOfWarMap[pos.z][pos.x][pos.y]) + { + bool hasInvisibleNeighbor = false; + + foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) + { + if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) + { + hasInvisibleNeighbor = true; + } + }); + + if(hasInvisibleNeighbor) + from.push_back(pos); + } + }); + + logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + + if(!bestGoal->invalid()) + { + return; + } + + allowDeadEndCancellation = false; + + for(int i = 0; i < sightRadius; i++) + { + getVisibleNeighbours(from, to); + vstd::concatenate(from, to); + vstd::removeDuplicates(from); + } + + logAi->debug("Exploration scan all possible tiles for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + } + + void scanTile(const int3 & tile) + { + if(tile == ourPos + || !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does + return; + + int tilesDiscovered = howManyTilesWillBeDiscovered(tile); + if(!tilesDiscovered) + return; + + auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy); + for(auto goal : waysToVisit) + { + if(goal->evaluationContext.movementCost <= 0.0) // should not happen + continue; + + float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; + + if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much + { + auto obj = cb->getTopObj(tile); + + // picking up resources does not yield any exploration at all. + // if it blocks the way to some explorable tile AIPathfinder will take care of it + if(obj && obj->isBlockedVisitable()) + { + continue; + } + + if(isSafeToVisit(hero, tile)) + { + bestGoal = goal; + bestValue = ourValue; + } + } + } + } + + void getVisibleNeighbours(const std::vector & tiles, std::vector & out) const + { + for(const int3 & tile : tiles) + { + foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) + { + if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) + { + out.push_back(neighbour); + } + }); + } + } + + int howManyTilesWillBeDiscovered(const int3 & pos) const + { + int ret = 0; + int3 npos = int3(0, 0, pos.z); + + const auto & slice = ts->fogOfWarMap[pos.z]; + + for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++) + { + for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++) + { + if(cbp->isInTheMap(npos) + && pos.dist2d(npos) - 0.5 < sightRadius + && !slice[npos.x][npos.y]) + { + if(allowDeadEndCancellation + && !hasReachableNeighbor(npos)) + { + continue; + } + + ret++; + } + } + } + + return ret; + } + + bool hasReachableNeighbor(const int3 &pos) const + { + for(crint3 dir : int3::getDirs()) + { + int3 tile = pos + dir; + if(cbp->isInTheMap(tile)) + { + auto isAccessible = aip->ah->isTileAccessible(hero, tile); + + if(isAccessible) + return true; + } + } + + return false; + } + }; +} + +bool Explore::operator==(const Explore & other) const +{ + return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; +} + +std::string Explore::completeMessage() const +{ + return "Hero " + hero.get()->getNameTranslated() + " completed exploration"; +} + +TSubgoal Explore::whatToDoToAchieve() +{ + return fh->chooseSolution(getAllPossibleSubgoals()); +} + +TGoalVec Explore::getAllPossibleSubgoals() +{ + TGoalVec ret; + std::vector heroes; + + if(hero) + { + heroes.push_back(hero.h); + } + else + { + //heroes = ai->getUnblockedHeroes(); + heroes = cb->getHeroesInfo(); + vstd::erase_if(heroes, [](const HeroPtr h) + { + if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer + return true; + + if(!ai->isAbleToExplore(h)) + return true; + + return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway + }); + } + + //try to use buildings that uncover map + std::vector objs; + for(auto obj : ai->visitableObjs) + { + if(!vstd::contains(ai->alreadyVisited, obj)) + { + switch(obj->ID.num) + { + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: + case Obj::CARTOGRAPHER: + objs.push_back(obj); + break; + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); + if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) + objs.push_back(obj); + break; + } + } + else + { + switch(obj->ID.num) + { + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) + break; + for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) + { + if(!cb->getObj(exit)) + { // Always attempt to visit two-way teleports if one of channel exits is not visible + objs.push_back(obj); + break; + } + } + break; + } + } + } + + for(auto h : heroes) + { + for(auto obj : objs) //double loop, performance risk? + { + auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy); + + vstd::concatenate(ret, waysToVisitObj); + } + + TSubgoal goal = exploreNearestNeighbour(h); + + if(!goal->invalid()) + { + ret.push_back(goal); + } + } + + if(ret.empty()) + { + for(auto h : heroes) + { + logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated()); + + TSubgoal goal = explorationNewPoint(h); + + if(goal->invalid()) + { + ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore + } + else + { + ret.push_back(goal); + } + } + } + + //we either don't have hero yet or none of heroes can explore + if((!hero || ret.empty()) && ai->canRecruitAnyHero()) + ret.push_back(sptr(RecruitHero())); + + if(ret.empty()) + { + throw goalFulfilledException(sptr(Explore().sethero(hero))); + } + + return ret; +} + +bool Explore::fulfillsMe(TSubgoal goal) +{ + if(goal->goalType == EXPLORE) + { + if(goal->hero) + return hero == goal->hero; + else + return true; //cancel ALL exploration + } + return false; +} + +TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const +{ + ExplorationHelper scanResult(h, allowGatherArmy); + + for(crint3 dir : int3::getDirs()) + { + int3 tile = hpos + dir; + if(cb->isInTheMap(tile)) + { + scanResult.scanTile(tile); + } + } + + return scanResult.bestGoal; +} + + +TSubgoal Explore::explorationNewPoint(HeroPtr h) const +{ + ExplorationHelper scanResult(h, allowGatherArmy); + + scanResult.scanSector(10); + + if(!scanResult.bestGoal->invalid()) + { + return scanResult.bestGoal; + } + + scanResult.scanMap(); + + return scanResult.bestGoal; +} + + +TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const +{ + TimeCheck tc("where to explore"); + int3 hpos = h->visitablePos(); + + //look for nearby objs -> visit them if they're close enough + const int DIST_LIMIT = 3; + const float COST_LIMIT = .2f; //todo: fine tune + + std::vector nearbyVisitableObjs; + for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map + { + for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y) + { + for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false)) + { + if(ai->isGoodForVisit(obj, h, COST_LIMIT)) + { + nearbyVisitableObjs.push_back(obj); + } + } + } + } + + if(nearbyVisitableObjs.size()) + { + vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles + boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); + + TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false)); + + if(!pickupNearestObj->invalid()) + { + return pickupNearestObj; + } + } + + //check if nearby tiles allow us to reveal anything - this is quick + return explorationBestNeighbour(hpos, h); +} diff --git a/AI/VCAI/Goals/Explore.h b/AI/VCAI/Goals/Explore.h index 5c5b29679..a96de7f29 100644 --- a/AI/VCAI/Goals/Explore.h +++ b/AI/VCAI/Goals/Explore.h @@ -1,66 +1,66 @@ -/* -* Explore.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#pragma once - -#include "CGoal.h" - -struct HeroPtr; -class VCAI; -class FuzzyHelper; - -namespace Goals -{ - struct ExplorationHelper; - - class DLL_EXPORT Explore : public CGoal - { - private: - bool allowGatherArmy; - - public: - Explore(bool allowGatherArmy) - : CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy) - { - priority = 1; - } - - Explore() - : Explore(true) - { - } - - Explore(HeroPtr h) - : CGoal(Goals::EXPLORE) - { - hero = h; - priority = 1; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - std::string completeMessage() const override; - bool fulfillsMe(TSubgoal goal) override; - bool operator==(const Explore & other) const override; - - private: - TSubgoal exploreNearestNeighbour(HeroPtr h) const; - TSubgoal explorationNewPoint(HeroPtr h) const; - TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const; - void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const; - bool hasReachableneighbour(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; - - void getVisibleNeighbours( - const std::vector & tiles, - std::vector & out, - CCallback * cbp, - const TeamState * ts) const; - - int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const; - }; -} +/* +* Explore.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + struct ExplorationHelper; + + class DLL_EXPORT Explore : public CGoal + { + private: + bool allowGatherArmy; + + public: + Explore(bool allowGatherArmy) + : CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy) + { + priority = 1; + } + + Explore() + : Explore(true) + { + } + + Explore(HeroPtr h) + : CGoal(Goals::EXPLORE) + { + hero = h; + priority = 1; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + std::string completeMessage() const override; + bool fulfillsMe(TSubgoal goal) override; + bool operator==(const Explore & other) const override; + + private: + TSubgoal exploreNearestNeighbour(HeroPtr h) const; + TSubgoal explorationNewPoint(HeroPtr h) const; + TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const; + void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const; + bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; + + void getVisibleNeighbours( + const std::vector & tiles, + std::vector & out, + CCallback * cbp, + const TeamState * ts) const; + + int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const; + }; +} diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 2206b24cb..cb23064ad 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -819,6 +819,9 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget) bool BattleFieldController::isTileAttackable(const BattleHex & number) const { + if(!number.isValid()) + return false; + for (auto & elem : occupiableHexes) { if (BattleHex::mutualPosition(elem, number) != -1 || elem == number) diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index ecb6237af..e14aa5cab 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -1,141 +1,141 @@ -/* - * BattleFieldController.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../../lib/battle/BattleHexArray.h" -#include "../../lib/Point.h" -#include "../gui/CIntObject.h" - -VCMI_LIB_NAMESPACE_BEGIN -class CStack; -class Rect; -VCMI_LIB_NAMESPACE_END - -class BattleHero; -class CAnimation; -class Canvas; -class IImage; -class BattleInterface; - -/// Handles battlefield grid as well as rendering of background layer of battle interface -class BattleFieldController : public CIntObject -{ - BattleInterface & owner; - - std::shared_ptr background; - std::shared_ptr cellBorder; - std::shared_ptr cellUnitMovementHighlight; - std::shared_ptr cellUnitMaxMovementHighlight; - std::shared_ptr cellShade; - std::shared_ptr rangedFullDamageLimitImages; - std::shared_ptr shootingRangeLimitImages; - - std::shared_ptr attackCursors; - std::shared_ptr spellCursors; - - /// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack - std::unique_ptr backgroundWithHexes; - - /// direction which will be used to perform attack with current cursor position - Point currentAttackOriginPoint; - - /// hex currently under mouse hover - BattleHex hoveredHex; - - /// hexes to which currently active stack can move - BattleHexArray occupiableHexes; - - /// hexes that when in front of a unit cause it's amount box to move back - std::array stackCountOutsideHexes; - - void showHighlightedHex(Canvas & to, std::shared_ptr highlight, BattleHex hex, bool darkBorder); - - BattleHexArray getHighlightedHexesForActiveStack(); - BattleHexArray getMovementRangeForHoveredStack(); - BattleHexArray getHighlightedHexesForSpellRange(); - BattleHexArray getHighlightedHexesForMovementTarget(); - - // Range limit highlight helpers - - /// get all hexes within a certain distance of given hex - BattleHexArray getRangeHexes(BattleHex sourceHex, uint8_t distance); - - /// get only hexes at the limit of a range - BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray hexRange, uint8_t distanceToLimit); - - /// calculate if a hex is in range limit and return its index in range - bool IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit); - - /// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists - std::vector> getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray rangeHexes, BattleHexArray rangeLimitHexes); - - /// calculates what image to use as range limit, depending on the direction of neighbours - /// a mask is used internally to mark the directions of all neighbours - /// based on this mask the corresponding image is selected - std::vector> calculateRangeLimitHighlightImages(std::vector> hexesNeighbourDirections, std::shared_ptr limitImages); - - /// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes - void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights); - - void showBackground(Canvas & canvas); - void showBackgroundImage(Canvas & canvas); - void showBackgroundImageWithHexes(Canvas & canvas); - void showHighlightedHexes(Canvas & canvas); - void updateAccessibleHexes(); - - BattleHex getHexAtPosition(Point hoverPosition); - - /// Checks whether selected pixel is transparent, uses local coordinates of a hex - bool isPixelInHex(Point const & position); - size_t selectBattleCursor(BattleHex myNumber); - - void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; - void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override; - void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override; - void clickPressed(const Point & cursorPosition) override; - void showPopupWindow(const Point & cursorPosition) override; - void activate() override; - - void showAll(Canvas & to) override; - void show(Canvas & to) override; - void tick(uint32_t msPassed) override; - - bool receiveEvent(const Point & position, int eventType) const override; - -public: - BattleFieldController(BattleInterface & owner); - - void createHeroes(); - - void redrawBackgroundWithHexes(); - void renderBattlefield(Canvas & canvas); - - /// Returns position of hex relative to owner (BattleInterface) - Rect hexPositionLocal(BattleHex hex) const; - - /// Returns position of hex relative to game window - Rect hexPositionAbsolute(BattleHex hex) const; - - /// Returns ID of currently hovered hex or BattleHex::INVALID if none - BattleHex getHoveredHex(); - - /// Returns the currently hovered stack - const CStack* getHoveredStack(); - - /// returns true if selected tile can be attacked in melee by current stack - bool isTileAttackable(const BattleHex & number) const; - - /// returns true if stack should render its stack count image in default position - outside own hex - bool stackCountOutsideHex(const BattleHex & number) const; - - BattleHex::EDir selectAttackDirection(BattleHex myNumber); - - BattleHex fromWhichHexAttack(BattleHex myNumber); -}; +/* + * BattleFieldController.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../../lib/battle/BattleHexArray.h" +#include "../../lib/Point.h" +#include "../gui/CIntObject.h" + +VCMI_LIB_NAMESPACE_BEGIN +class CStack; +class Rect; +VCMI_LIB_NAMESPACE_END + +class BattleHero; +class CAnimation; +class Canvas; +class IImage; +class BattleInterface; + +/// Handles battlefield grid as well as rendering of background layer of battle interface +class BattleFieldController : public CIntObject +{ + BattleInterface & owner; + + std::shared_ptr background; + std::shared_ptr cellBorder; + std::shared_ptr cellUnitMovementHighlight; + std::shared_ptr cellUnitMaxMovementHighlight; + std::shared_ptr cellShade; + std::shared_ptr rangedFullDamageLimitImages; + std::shared_ptr shootingRangeLimitImages; + + std::shared_ptr attackCursors; + std::shared_ptr spellCursors; + + /// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack + std::unique_ptr backgroundWithHexes; + + /// direction which will be used to perform attack with current cursor position + Point currentAttackOriginPoint; + + /// hex currently under mouse hover + BattleHex hoveredHex; + + /// hexes to which currently active stack can move + BattleHexArray occupiableHexes; + + /// hexes that when in front of a unit cause it's amount box to move back + std::array stackCountOutsideHexes; + + void showHighlightedHex(Canvas & to, std::shared_ptr highlight, BattleHex hex, bool darkBorder); + + BattleHexArray getHighlightedHexesForActiveStack(); + BattleHexArray getMovementRangeForHoveredStack(); + BattleHexArray getHighlightedHexesForSpellRange(); + BattleHexArray getHighlightedHexesForMovementTarget(); + + // Range limit highlight helpers + + /// get all hexes within a certain distance of given hex + BattleHexArray getRangeHexes(BattleHex sourceHex, uint8_t distance); + + /// get only hexes at the limit of a range + BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray hexRange, uint8_t distanceToLimit); + + /// calculate if a hex is in range limit and return its index in range + bool IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit); + + /// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists + std::vector> getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray rangeHexes, BattleHexArray rangeLimitHexes); + + /// calculates what image to use as range limit, depending on the direction of neighbours + /// a mask is used internally to mark the directions of all neighbours + /// based on this mask the corresponding image is selected + std::vector> calculateRangeLimitHighlightImages(std::vector> hexesNeighbourDirections, std::shared_ptr limitImages); + + /// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes + void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights); + + void showBackground(Canvas & canvas); + void showBackgroundImage(Canvas & canvas); + void showBackgroundImageWithHexes(Canvas & canvas); + void showHighlightedHexes(Canvas & canvas); + void updateAccessibleHexes(); + + BattleHex getHexAtPosition(Point hoverPosition); + + /// Checks whether selected pixel is transparent, uses local coordinates of a hex + bool isPixelInHex(Point const & position); + size_t selectBattleCursor(BattleHex myNumber); + + void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; + void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override; + void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override; + void clickPressed(const Point & cursorPosition) override; + void showPopupWindow(const Point & cursorPosition) override; + void activate() override; + + void showAll(Canvas & to) override; + void show(Canvas & to) override; + void tick(uint32_t msPassed) override; + + bool receiveEvent(const Point & position, int eventType) const override; + +public: + BattleFieldController(BattleInterface & owner); + + void createHeroes(); + + void redrawBackgroundWithHexes(); + void renderBattlefield(Canvas & canvas); + + /// Returns position of hex relative to owner (BattleInterface) + Rect hexPositionLocal(BattleHex hex) const; + + /// Returns position of hex relative to game window + Rect hexPositionAbsolute(BattleHex hex) const; + + /// Returns ID of currently hovered hex or BattleHex::INVALID if none + BattleHex getHoveredHex(); + + /// Returns the currently hovered stack + const CStack* getHoveredStack(); + + /// returns true if selected tile can be attacked in melee by current stack + bool isTileAttackable(const BattleHex & number) const; + + /// returns true if stack should render its stack count image in default position - outside own hex + bool stackCountOutsideHex(const BattleHex & number) const; + + BattleHex::EDir selectAttackDirection(BattleHex myNumber); + + BattleHex fromWhichHexAttack(BattleHex myNumber); +}; diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index 219143b06..43a529fd4 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -1,232 +1,232 @@ -/* - * BattleInterface.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "BattleConstants.h" -#include "../gui/CIntObject.h" -#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation -#include "../ConditionalWait.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class CCreatureSet; -class CGHeroInstance; -class CStack; -struct BattleResult; -struct BattleSpellCast; -struct CObstacleInstance; -struct SetStackEffect; -class BattleAction; -class CGTownInstance; -struct CatapultAttack; -struct BattleTriggerEffect; -struct BattleHex; -struct InfoAboutHero; -class ObstacleChanges; -class CPlayerBattleCallback; - -VCMI_LIB_NAMESPACE_END - -class BattleHero; -class Canvas; -class BattleResultWindow; -class StackQueue; -class CPlayerInterface; -struct BattleEffect; -class IImage; -class StackQueue; - -class BattleProjectileController; -class BattleSiegeController; -class BattleObstacleController; -class BattleFieldController; -class BattleRenderer; -class BattleWindow; -class BattleStacksController; -class BattleActionsController; -class BattleEffectsController; -class BattleConsole; - -/// Small struct which contains information about the id of the attacked stack, the damage dealt,... -struct StackAttackedInfo -{ - const CStack *defender; - const CStack *attacker; - - int64_t damageDealt; - uint32_t amountKilled; - SpellID spellEffect; - - bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack - bool killed; //if true, stack has been killed - bool rebirth; //if true, play rebirth animation after all - bool cloneKilled; - bool fireShield; -}; - -struct StackAttackInfo -{ - const CStack *attacker; - const CStack *defender; - std::vector< const CStack *> secondaryDefender; - - SpellID spellEffect; - BattleHex tile; - - bool indirectAttack; - bool lucky; - bool unlucky; - bool deathBlow; - bool lifeDrain; -}; - -/// Main class for battles, responsible for relaying information from server to various battle entities -class BattleInterface -{ - using AwaitingAnimationAction = std::function; - - struct AwaitingAnimationEvents { - AwaitingAnimationAction action; - EAnimationEvents event; - }; - - /// Conditional variables that are set depending on ongoing animations on the battlefield - ConditionalWait ongoingAnimationsState; - - /// List of events that are waiting to be triggered - std::vector awaitingEvents; - - /// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players - std::shared_ptr tacticianInterface; - - /// attacker interface, not null if attacker is human in our vcmiclient - std::shared_ptr attackerInt; - - /// defender interface, not null if attacker is human in our vcmiclient - std::shared_ptr defenderInt; - - /// if set to true, battle is still starting and waiting for intro sound to end / key press from player - bool battleOpeningDelayActive; - - /// ID of ongoing battle - BattleID battleID; - - void playIntroSoundAndUnlockInterface(); - void onIntroSoundPlayed(); -public: - /// copy of initial armies (for result window) - const CCreatureSet *army1; - const CCreatureSet *army2; - - std::shared_ptr windowObject; - std::shared_ptr console; - - /// currently active player interface - std::shared_ptr curInt; - - const CGHeroInstance *attackingHeroInstance; - const CGHeroInstance *defendingHeroInstance; - - bool tacticsMode; - ui32 round; - - std::unique_ptr projectilesController; - std::unique_ptr siegeController; - std::unique_ptr obstacleController; - std::unique_ptr fieldController; - std::unique_ptr stacksController; - std::unique_ptr actionsController; - std::unique_ptr effectsController; - - std::shared_ptr attackingHero; - std::shared_ptr defendingHero; - - bool openingPlaying() const; - void openingEnd(); - - bool makingTurn() const; - - BattleID getBattleID() const; - std::shared_ptr getBattle() const; - - BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); - ~BattleInterface(); - - void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player - void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all - void requestAutofightingAIToTakeAction(); - - void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE); - void sendCommand(BattleAction command, const CStack * actor = nullptr); - - const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell - - void showInterface(Canvas & to); - - void setHeroAnimation(BattleSide side, EHeroAnimType phase); - - void executeSpellCast(); //called when a hero casts a spell - - void appendBattleLog(const std::string & newEntry); - - void redrawBattlefield(); //refresh GUI after changing stack range / grid settings - CPlayerInterface *getCurrentPlayerInterface() const; - - void tacticNextStack(const CStack *current); - void tacticPhaseEnd(); - - void setBattleQueueVisibility(bool visible); - void setStickyHeroWindowsVisibility(bool visible); - void setStickyQuickSpellWindowVisibility(bool visible); - - void endNetwork(); - void executeStagedAnimations(); - void executeAnimationStage( EAnimationEvents event); - void onAnimationsStarted(); - void onAnimationsFinished(); - void waitForAnimations(); - bool hasAnimations(); - void checkForAnimations(); - void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action); - - //call-ins - void startAction(const BattleAction & action); - void stackReset(const CStack * stack); - void stackAdded(const CStack * stack); //new stack appeared on battlefield - void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled - void stackActivated(const CStack *stack); //active stack has been changed - void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport); //stack with id number moved to destHex - void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked - void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest - void newRoundFirst(); - void newRound(); //called when round is ended; - void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls - void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed - void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell - void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks - void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook - - void displayBattleLog(const std::vector & battleLog); - - void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit); - void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation - void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation - void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation - - void endAction(const BattleAction & action); - - void obstaclePlaced(const std::vector> oi); - void obstacleRemoved(const std::vector & obstacles); - - void gateStateChanged(const EGateState state); - - const CGHeroInstance *currentHero() const; - InfoAboutHero enemyHero() const; -}; +/* + * BattleInterface.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "BattleConstants.h" +#include "../gui/CIntObject.h" +#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation +#include "../ConditionalWait.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CCreatureSet; +class CGHeroInstance; +class CStack; +struct BattleResult; +struct BattleSpellCast; +struct CObstacleInstance; +struct SetStackEffect; +class BattleAction; +class CGTownInstance; +struct CatapultAttack; +struct BattleTriggerEffect; +struct BattleHex; +struct InfoAboutHero; +class ObstacleChanges; +class CPlayerBattleCallback; + +VCMI_LIB_NAMESPACE_END + +class BattleHero; +class Canvas; +class BattleResultWindow; +class StackQueue; +class CPlayerInterface; +struct BattleEffect; +class IImage; +class StackQueue; + +class BattleProjectileController; +class BattleSiegeController; +class BattleObstacleController; +class BattleFieldController; +class BattleRenderer; +class BattleWindow; +class BattleStacksController; +class BattleActionsController; +class BattleEffectsController; +class BattleConsole; + +/// Small struct which contains information about the id of the attacked stack, the damage dealt,... +struct StackAttackedInfo +{ + const CStack *defender; + const CStack *attacker; + + int64_t damageDealt; + uint32_t amountKilled; + SpellID spellEffect; + + bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack + bool killed; //if true, stack has been killed + bool rebirth; //if true, play rebirth animation after all + bool cloneKilled; + bool fireShield; +}; + +struct StackAttackInfo +{ + const CStack *attacker; + const CStack *defender; + std::vector< const CStack *> secondaryDefender; + + SpellID spellEffect; + BattleHex tile; + + bool indirectAttack; + bool lucky; + bool unlucky; + bool deathBlow; + bool lifeDrain; +}; + +/// Main class for battles, responsible for relaying information from server to various battle entities +class BattleInterface +{ + using AwaitingAnimationAction = std::function; + + struct AwaitingAnimationEvents { + AwaitingAnimationAction action; + EAnimationEvents event; + }; + + /// Conditional variables that are set depending on ongoing animations on the battlefield + ConditionalWait ongoingAnimationsState; + + /// List of events that are waiting to be triggered + std::vector awaitingEvents; + + /// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players + std::shared_ptr tacticianInterface; + + /// attacker interface, not null if attacker is human in our vcmiclient + std::shared_ptr attackerInt; + + /// defender interface, not null if attacker is human in our vcmiclient + std::shared_ptr defenderInt; + + /// if set to true, battle is still starting and waiting for intro sound to end / key press from player + bool battleOpeningDelayActive; + + /// ID of ongoing battle + BattleID battleID; + + void playIntroSoundAndUnlockInterface(); + void onIntroSoundPlayed(); +public: + /// copy of initial armies (for result window) + const CCreatureSet *army1; + const CCreatureSet *army2; + + std::shared_ptr windowObject; + std::shared_ptr console; + + /// currently active player interface + std::shared_ptr curInt; + + const CGHeroInstance *attackingHeroInstance; + const CGHeroInstance *defendingHeroInstance; + + bool tacticsMode; + ui32 round; + + std::unique_ptr projectilesController; + std::unique_ptr siegeController; + std::unique_ptr obstacleController; + std::unique_ptr fieldController; + std::unique_ptr stacksController; + std::unique_ptr actionsController; + std::unique_ptr effectsController; + + std::shared_ptr attackingHero; + std::shared_ptr defendingHero; + + bool openingPlaying() const; + void openingEnd(); + + bool makingTurn() const; + + BattleID getBattleID() const; + std::shared_ptr getBattle() const; + + BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); + ~BattleInterface(); + + void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player + void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all + void requestAutofightingAIToTakeAction(); + + void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE); + void sendCommand(BattleAction command, const CStack * actor = nullptr); + + const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell + + void showInterface(Canvas & to); + + void setHeroAnimation(BattleSide side, EHeroAnimType phase); + + void executeSpellCast(); //called when a hero casts a spell + + void appendBattleLog(const std::string & newEntry); + + void redrawBattlefield(); //refresh GUI after changing stack range / grid settings + CPlayerInterface *getCurrentPlayerInterface() const; + + void tacticNextStack(const CStack *current); + void tacticPhaseEnd(); + + void setBattleQueueVisibility(bool visible); + void setStickyHeroWindowsVisibility(bool visible); + void setStickyQuickSpellWindowVisibility(bool visible); + + void endNetwork(); + void executeStagedAnimations(); + void executeAnimationStage( EAnimationEvents event); + void onAnimationsStarted(); + void onAnimationsFinished(); + void waitForAnimations(); + bool hasAnimations(); + void checkForAnimations(); + void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action); + + //call-ins + void startAction(const BattleAction & action); + void stackReset(const CStack * stack); + void stackAdded(const CStack * stack); //new stack appeared on battlefield + void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled + void stackActivated(const CStack *stack); //active stack has been changed + void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport); //stack with id number moved to destHex + void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked + void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest + void newRoundFirst(); + void newRound(); //called when round is ended; + void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls + void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed + void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell + void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks + void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook + + void displayBattleLog(const std::vector & battleLog); + + void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit); + void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation + void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation + void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation + + void endAction(const BattleAction & action); + + void obstaclePlaced(const std::vector> oi); + void obstacleRemoved(const std::vector & obstacles); + + void gateStateChanged(const EGateState state); + + const CGHeroInstance *currentHero() const; + InfoAboutHero enemyHero() const; +}; diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 3f6862343..f13f9fca4 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -1,149 +1,149 @@ -/* - * BattleStacksController.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../render/ColorFilter.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleHex; -class BattleHexArray; -class BattleAction; -class CStack; -class CSpell; -class SpellID; -class Point; - -VCMI_LIB_NAMESPACE_END - -struct StackAttackedInfo; -struct StackAttackInfo; - -class ColorFilter; -class Canvas; -class BattleInterface; -class BattleAnimation; -class CreatureAnimation; -class BattleAnimation; -class BattleRenderer; -class IImage; - -struct BattleStackFilterEffect -{ - ColorFilter effect; - const CStack * target; - const CSpell * source; - bool persistent; -}; - -/// Class responsible for handling stacks in battle -/// Handles ordering of stacks animation -/// As well as rendering of stacks, their amount boxes -/// And any other effect applied to stacks -class BattleStacksController -{ - BattleInterface & owner; - - std::shared_ptr amountNormal; - std::shared_ptr amountNegative; - std::shared_ptr amountPositive; - std::shared_ptr amountEffNeutral; - - /// currently displayed animations - std::vector currentAnimations; - - /// currently active color effects on stacks, in order of their addition (which corresponds to their apply order) - std::vector stackFilterEffects; - - /// animations of creatures from fighting armies (order by BattleInfo's stacks' ID) - std::map> stackAnimation; - - /// //TODO: move it to battle callback - std::map stackFacingRight; - - /// Stacks have amount box hidden due to ongoing animations - std::set stackAmountBoxHidden; - - /// currently active stack; nullptr - no one - const CStack *activeStack; - - /// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation - std::vector mouseHoveredStacks; - - ///when animation is playing, we should wait till the end to make the next stack active; nullptr of none - const CStack *stackToActivate; - - /// for giving IDs for animations - ui32 animIDhelper; - - bool stackNeedsAmountBox(const CStack * stack) const; - void showStackAmountBox(Canvas & canvas, const CStack * stack); - BattleHex getStackCurrentPosition(const CStack * stack) const; - - std::shared_ptr getStackAmountBox(const CStack * stack); - - void removeExpiredColorFilters(); - - void initializeBattleAnimations(); - void tickFrameBattleAnimations(uint32_t msPassed); - - void updateBattleAnimations(uint32_t msPassed); - - std::vector selectHoveredStacks(); - - bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender); - -public: - BattleStacksController(BattleInterface & owner); - - bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const; - bool facingRight(const CStack * stack) const; - - void stackReset(const CStack * stack); - void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield - void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled - void stackActivated(const CStack *stack); //active stack has been changed - void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex - void stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex - void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked - void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest - - void startAction(const BattleAction & action); - void endAction(const BattleAction & action); - - void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack - - void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack - - void setActiveStack(const CStack *stack); - - void showAliveStack(Canvas & canvas, const CStack * stack); - void showStack(Canvas & canvas, const CStack * stack); - - void updateHoveredStacks(); - - void collectRenderableObjects(BattleRenderer & renderer); - - /// Adds new color filter effect targeting stack - /// Effect will last as long as stack is affected by specified spell (unless effect is persistent) - /// If effect from same (target, source) already exists, it will be updated - void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent); - void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims - - const CStack* getActiveStack() const; - const std::vector getHoveredStacksUnitIds() const; - - void tick(uint32_t msPassed); - - /// returns position of animation needed to place stack in specific hex - Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const; - - friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations -}; +/* + * BattleStacksController.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../render/ColorFilter.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleHex; +class BattleHexArray; +class BattleAction; +class CStack; +class CSpell; +class SpellID; +class Point; + +VCMI_LIB_NAMESPACE_END + +struct StackAttackedInfo; +struct StackAttackInfo; + +class ColorFilter; +class Canvas; +class BattleInterface; +class BattleAnimation; +class CreatureAnimation; +class BattleAnimation; +class BattleRenderer; +class IImage; + +struct BattleStackFilterEffect +{ + ColorFilter effect; + const CStack * target; + const CSpell * source; + bool persistent; +}; + +/// Class responsible for handling stacks in battle +/// Handles ordering of stacks animation +/// As well as rendering of stacks, their amount boxes +/// And any other effect applied to stacks +class BattleStacksController +{ + BattleInterface & owner; + + std::shared_ptr amountNormal; + std::shared_ptr amountNegative; + std::shared_ptr amountPositive; + std::shared_ptr amountEffNeutral; + + /// currently displayed animations + std::vector currentAnimations; + + /// currently active color effects on stacks, in order of their addition (which corresponds to their apply order) + std::vector stackFilterEffects; + + /// animations of creatures from fighting armies (order by BattleInfo's stacks' ID) + std::map> stackAnimation; + + /// //TODO: move it to battle callback + std::map stackFacingRight; + + /// Stacks have amount box hidden due to ongoing animations + std::set stackAmountBoxHidden; + + /// currently active stack; nullptr - no one + const CStack *activeStack; + + /// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation + std::vector mouseHoveredStacks; + + ///when animation is playing, we should wait till the end to make the next stack active; nullptr of none + const CStack *stackToActivate; + + /// for giving IDs for animations + ui32 animIDhelper; + + bool stackNeedsAmountBox(const CStack * stack) const; + void showStackAmountBox(Canvas & canvas, const CStack * stack); + BattleHex getStackCurrentPosition(const CStack * stack) const; + + std::shared_ptr getStackAmountBox(const CStack * stack); + + void removeExpiredColorFilters(); + + void initializeBattleAnimations(); + void tickFrameBattleAnimations(uint32_t msPassed); + + void updateBattleAnimations(uint32_t msPassed); + + std::vector selectHoveredStacks(); + + bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender); + +public: + BattleStacksController(BattleInterface & owner); + + bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const; + bool facingRight(const CStack * stack) const; + + void stackReset(const CStack * stack); + void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield + void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled + void stackActivated(const CStack *stack); //active stack has been changed + void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex + void stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex + void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked + void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest + + void startAction(const BattleAction & action); + void endAction(const BattleAction & action); + + void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack + + void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack + + void setActiveStack(const CStack *stack); + + void showAliveStack(Canvas & canvas, const CStack * stack); + void showStack(Canvas & canvas, const CStack * stack); + + void updateHoveredStacks(); + + void collectRenderableObjects(BattleRenderer & renderer); + + /// Adds new color filter effect targeting stack + /// Effect will last as long as stack is affected by specified spell (unless effect is persistent) + /// If effect from same (target, source) already exists, it will be updated + void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent); + void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims + + const CStack* getActiveStack() const; + const std::vector getHoveredStacksUnitIds() const; + + void tick(uint32_t msPassed); + + /// returns position of animation needed to place stack in specific hex + Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const; + + friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations +}; diff --git a/include/vstd/RNG.h b/include/vstd/RNG.h index d42ba0a73..9762e730d 100644 --- a/include/vstd/RNG.h +++ b/include/vstd/RNG.h @@ -1,104 +1,104 @@ -/* - * RNG.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -VCMI_LIB_NAMESPACE_BEGIN - -namespace vstd -{ - -class DLL_LINKAGE RNG -{ -public: - virtual ~RNG() = default; - - /// Returns random number in range [lower, upper] - virtual int nextInt(int lower, int upper) = 0; - - /// Returns random number in range [lower, upper] - virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0; - - /// Returns random number in range [lower, upper] - virtual double nextDouble(double lower, double upper) = 0; - - /// Returns random number in range [0, upper] - virtual int nextInt(int upper) = 0; - - /// Returns random number in range [0, upper] - virtual int64_t nextInt64(int64_t upper) = 0; - - /// Returns random number in range [0, upper] - virtual double nextDouble(double upper) = 0; - - /// Generates an integer between 0 and the maximum value it can hold. - /// Should be only used for seeding other generators - virtual int nextInt() = 0; - - /// Returns integer using binomial distribution - /// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts - virtual int nextBinomialInt(int coinsCount, double coinChance) = 0; -}; - -} - -namespace RandomGeneratorUtil -{ - template - auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) - { - if(container.empty()) - throw std::runtime_error("Unable to select random item from empty container!"); - - return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); - } - - template - auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) - { - if(container.empty()) - throw std::runtime_error("Unable to select random item from empty container!"); - - return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); - } - - template - size_t nextItemWeighted(Container & container, vstd::RNG & rand) - { - assert(!container.empty()); - - int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0); - assert(totalWeight > 0); - - int64_t roll = rand.nextInt64(0, totalWeight - 1); - - for (size_t i = 0; i < container.size(); ++i) - { - roll -= container[i]; - if(roll < 0) - return i; - } - return container.size() - 1; - } - - template - void randomShuffle(Container & container, vstd::RNG & rand) - { - int64_t n = std::distance(container.begin(), container.end()); - - for(int64_t i = n - 1; i > 0; --i) - { - auto randIndex = rand.nextInt64(0, i); - std::swap(*(container.begin() + i), *(container.begin() + randIndex)); - } - } -} - -VCMI_LIB_NAMESPACE_END +/* + * RNG.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +namespace vstd +{ + +class DLL_LINKAGE RNG +{ +public: + virtual ~RNG() = default; + + /// Returns random number in range [lower, upper] + virtual int nextInt(int lower, int upper) = 0; + + /// Returns random number in range [lower, upper] + virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0; + + /// Returns random number in range [lower, upper] + virtual double nextDouble(double lower, double upper) = 0; + + /// Returns random number in range [0, upper] + virtual int nextInt(int upper) = 0; + + /// Returns random number in range [0, upper] + virtual int64_t nextInt64(int64_t upper) = 0; + + /// Returns random number in range [0, upper] + virtual double nextDouble(double upper) = 0; + + /// Generates an integer between 0 and the maximum value it can hold. + /// Should be only used for seeding other generators + virtual int nextInt() = 0; + + /// Returns integer using binomial distribution + /// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts + virtual int nextBinomialInt(int coinsCount, double coinChance) = 0; +}; + +} + +namespace RandomGeneratorUtil +{ + template + auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); + } + + template + auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); + } + + template + size_t nextItemWeighted(Container & container, vstd::RNG & rand) + { + assert(!container.empty()); + + int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0); + assert(totalWeight > 0); + + int64_t roll = rand.nextInt64(0, totalWeight - 1); + + for (size_t i = 0; i < container.size(); ++i) + { + roll -= container[i]; + if(roll < 0) + return i; + } + return container.size() - 1; + } + + template + void randomShuffle(Container & container, vstd::RNG & rand) + { + int64_t n = std::distance(container.begin(), container.end()); + + for(int64_t i = n - 1; i > 0; --i) + { + auto randIndex = rand.nextInt64(0, i); + std::swap(*(container.begin() + i), *(container.begin() + randIndex)); + } + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index 7746aebb2..583299e8c 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -1,101 +1,101 @@ -/* - * BattleFieldHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include -#include "BattleFieldHandler.h" -#include "json/JsonBonus.h" - -VCMI_LIB_NAMESPACE_BEGIN - -std::shared_ptr BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) -{ - assert(identifier.find(':') == std::string::npos); - - auto info = std::make_shared(BattleField(index), identifier); - - info->modScope = scope; - info->graphics = ImagePath::fromJson(json["graphics"]); - info->icon = json["icon"].String(); - info->name = json["name"].String(); - for(const auto & b : json["bonuses"].Vector()) - { - auto bonus = JsonUtils::parseBonus(b); - - bonus->source = BonusSource::TERRAIN_OVERLAY; - bonus->sid = BonusSourceID(info->getId()); - bonus->duration = BonusDuration::ONE_BATTLE; - - info->bonuses.push_back(bonus); - } - - info->isSpecial = json["isSpecial"].Bool(); - for(auto node : json["impassableHexes"].Vector()) - info->impassableHexes.insert(node.Integer()); - - info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]); - info->musicFilename = AudioPath::fromJson(json["music"]); - - return info; -} - -std::vector BattleFieldHandler::loadLegacyData() -{ - return std::vector(); -} - -const std::vector & BattleFieldHandler::getTypeNames() const -{ - static const auto types = std::vector { "battlefield" }; - - return types; -} - -int32_t BattleFieldInfo::getIndex() const -{ - return battlefield.getNum(); -} - -int32_t BattleFieldInfo::getIconIndex() const -{ - return iconIndex; -} - -std::string BattleFieldInfo::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string BattleFieldInfo::getModScope() const -{ - return modScope; -} - -std::string BattleFieldInfo::getNameTextID() const -{ - return name; -} - -std::string BattleFieldInfo::getNameTranslated() const -{ - return name; // TODO? -} - -void BattleFieldInfo::registerIcons(const IconRegistar & cb) const -{ - //cb(getIconIndex(), "BATTLEFIELD", icon); -} - -BattleField BattleFieldInfo::getId() const -{ - return battlefield; -} - -VCMI_LIB_NAMESPACE_END +/* + * BattleFieldHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include +#include "BattleFieldHandler.h" +#include "json/JsonBonus.h" + +VCMI_LIB_NAMESPACE_BEGIN + +std::shared_ptr BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + + auto info = std::make_shared(BattleField(index), identifier); + + info->modScope = scope; + info->graphics = ImagePath::fromJson(json["graphics"]); + info->icon = json["icon"].String(); + info->name = json["name"].String(); + for(const auto & b : json["bonuses"].Vector()) + { + auto bonus = JsonUtils::parseBonus(b); + + bonus->source = BonusSource::TERRAIN_OVERLAY; + bonus->sid = BonusSourceID(info->getId()); + bonus->duration = BonusDuration::ONE_BATTLE; + + info->bonuses.push_back(bonus); + } + + info->isSpecial = json["isSpecial"].Bool(); + for(auto node : json["impassableHexes"].Vector()) + info->impassableHexes.insert(node.Integer()); + + info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]); + info->musicFilename = AudioPath::fromJson(json["music"]); + + return info; +} + +std::vector BattleFieldHandler::loadLegacyData() +{ + return std::vector(); +} + +const std::vector & BattleFieldHandler::getTypeNames() const +{ + static const auto types = std::vector { "battlefield" }; + + return types; +} + +int32_t BattleFieldInfo::getIndex() const +{ + return battlefield.getNum(); +} + +int32_t BattleFieldInfo::getIconIndex() const +{ + return iconIndex; +} + +std::string BattleFieldInfo::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string BattleFieldInfo::getModScope() const +{ + return modScope; +} + +std::string BattleFieldInfo::getNameTextID() const +{ + return name; +} + +std::string BattleFieldInfo::getNameTranslated() const +{ + return name; // TODO? +} + +void BattleFieldInfo::registerIcons(const IconRegistar & cb) const +{ + //cb(getIconIndex(), "BATTLEFIELD", icon); +} + +BattleField BattleFieldInfo::getId() const +{ + return battlefield; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h index 583b350fd..9e19213a7 100644 --- a/lib/BattleFieldHandler.h +++ b/lib/BattleFieldHandler.h @@ -1,80 +1,80 @@ -/* - * BattleFieldHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include "bonuses/Bonus.h" -#include "GameConstants.h" -#include "IHandlerBase.h" -#include "battle/BattleHexArray.h" -#include "filesystem/ResourcePath.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class BattleFieldInfo : public EntityT -{ -public: - BattleField battlefield; - std::vector> bonuses; - bool isSpecial; - ImagePath graphics; - std::string name; - std::string modScope; - std::string identifier; - std::string icon; - si32 iconIndex; - BattleHexArray impassableHexes; - AudioPath openingSoundFilename; - AudioPath musicFilename; - - BattleFieldInfo() - : BattleFieldInfo(BattleField::NONE, "") - { - } - - BattleFieldInfo(BattleField battlefield, std::string identifier): - isSpecial(false), - battlefield(battlefield), - identifier(identifier), - iconIndex(battlefield.getNum()), - name(identifier) - { - } - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - std::string getNameTextID() const override; - std::string getNameTranslated() const override; - void registerIcons(const IconRegistar & cb) const override; - BattleField getId() const override; -}; - -class DLL_LINKAGE BattleFieldService : public EntityServiceT -{ -public: -}; - -class BattleFieldHandler : public CHandlerBase -{ -public: - std::shared_ptr loadFromJson( - const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - const std::vector & getTypeNames() const override; - std::vector loadLegacyData() override; -}; - -VCMI_LIB_NAMESPACE_END +/* + * BattleFieldHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include +#include +#include "bonuses/Bonus.h" +#include "GameConstants.h" +#include "IHandlerBase.h" +#include "battle/BattleHexArray.h" +#include "filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class BattleFieldInfo : public EntityT +{ +public: + BattleField battlefield; + std::vector> bonuses; + bool isSpecial; + ImagePath graphics; + std::string name; + std::string modScope; + std::string identifier; + std::string icon; + si32 iconIndex; + BattleHexArray impassableHexes; + AudioPath openingSoundFilename; + AudioPath musicFilename; + + BattleFieldInfo() + : BattleFieldInfo(BattleField::NONE, "") + { + } + + BattleFieldInfo(BattleField battlefield, std::string identifier): + isSpecial(false), + battlefield(battlefield), + identifier(identifier), + iconIndex(battlefield.getNum()), + name(identifier) + { + } + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; + void registerIcons(const IconRegistar & cb) const override; + BattleField getId() const override; +}; + +class DLL_LINKAGE BattleFieldService : public EntityServiceT +{ +public: +}; + +class BattleFieldHandler : public CHandlerBase +{ +public: + std::shared_ptr loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index c438773ca..d21fba7ab 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -1,129 +1,129 @@ -/* - * ObstacleHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "ObstacleHandler.h" -#include "BattleFieldHandler.h" -#include "json/JsonNode.h" -#include "modding/IdentifierStorage.h" -#include "VCMI_Lib.h" - -VCMI_LIB_NAMESPACE_BEGIN - -int32_t ObstacleInfo::getIndex() const -{ - return obstacle.getNum(); -} - -int32_t ObstacleInfo::getIconIndex() const -{ - return iconIndex; -} - -std::string ObstacleInfo::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string ObstacleInfo::getModScope() const -{ - return modScope; -} - -std::string ObstacleInfo::getNameTranslated() const -{ - return identifier; -} - -std::string ObstacleInfo::getNameTextID() const -{ - return identifier; // TODO? -} - -void ObstacleInfo::registerIcons(const IconRegistar & cb) const -{ -} - -Obstacle ObstacleInfo::getId() const -{ - return obstacle; -} - -BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const -{ - if(isAbsoluteObstacle) - { - assert(!hex.isValid()); - return BattleHexArray(blockedTiles); - } - - BattleHexArray ret; - for(int offset : blockedTiles) - { - BattleHex toBlock = hex + offset; - if((hex.getY() & 1) && !(toBlock.getY() & 1)) - toBlock += BattleHex::LEFT; - - if(!toBlock.isValid()) - logGlobal->error("Misplaced obstacle!"); - else - ret.insert(toBlock); - } - - return ret; -} - -bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const -{ - const auto * bgInfo = battlefield.getInfo(); - - if(bgInfo->isSpecial) - return vstd::contains(allowedSpecialBfields, bgInfo->identifier); - - return vstd::contains(allowedTerrains, terrainType); -} - -std::shared_ptr ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) -{ - assert(identifier.find(':') == std::string::npos); - - auto info = std::make_shared(Obstacle(index), identifier); - - info->modScope = scope; - info->animation = AnimationPath::fromJson(json["animation"]); - info->width = json["width"].Integer(); - info->height = json["height"].Integer(); - for(const auto & t : json["allowedTerrains"].Vector()) - { - VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){ - info->allowedTerrains.emplace_back(identifier); - }); - } - for(const auto & t : json["specialBattlefields"].Vector()) - - info->allowedSpecialBfields.emplace_back(t.String()); - info->blockedTiles = json["blockedTiles"].convertTo>(); - info->isAbsoluteObstacle = json["absolute"].Bool(); - info->isForegroundObstacle = json["foreground"].Bool(); - - return info; -} - -std::vector ObstacleHandler::loadLegacyData() -{ - return {}; -} - -const std::vector & ObstacleHandler::getTypeNames() const -{ - static const std::vector types = { "obstacle" }; - return types; -} - -VCMI_LIB_NAMESPACE_END +/* + * ObstacleHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "ObstacleHandler.h" +#include "BattleFieldHandler.h" +#include "json/JsonNode.h" +#include "modding/IdentifierStorage.h" +#include "VCMI_Lib.h" + +VCMI_LIB_NAMESPACE_BEGIN + +int32_t ObstacleInfo::getIndex() const +{ + return obstacle.getNum(); +} + +int32_t ObstacleInfo::getIconIndex() const +{ + return iconIndex; +} + +std::string ObstacleInfo::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string ObstacleInfo::getModScope() const +{ + return modScope; +} + +std::string ObstacleInfo::getNameTranslated() const +{ + return identifier; +} + +std::string ObstacleInfo::getNameTextID() const +{ + return identifier; // TODO? +} + +void ObstacleInfo::registerIcons(const IconRegistar & cb) const +{ +} + +Obstacle ObstacleInfo::getId() const +{ + return obstacle; +} + +BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const +{ + if(isAbsoluteObstacle) + { + assert(!hex.isValid()); + return BattleHexArray(blockedTiles); + } + + BattleHexArray ret; + for(int offset : blockedTiles) + { + BattleHex toBlock = hex + offset; + if((hex.getY() & 1) && !(toBlock.getY() & 1)) + toBlock += BattleHex::LEFT; + + if(!toBlock.isValid()) + logGlobal->error("Misplaced obstacle!"); + else + ret.insert(toBlock); + } + + return ret; +} + +bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const +{ + const auto * bgInfo = battlefield.getInfo(); + + if(bgInfo->isSpecial) + return vstd::contains(allowedSpecialBfields, bgInfo->identifier); + + return vstd::contains(allowedTerrains, terrainType); +} + +std::shared_ptr ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + + auto info = std::make_shared(Obstacle(index), identifier); + + info->modScope = scope; + info->animation = AnimationPath::fromJson(json["animation"]); + info->width = json["width"].Integer(); + info->height = json["height"].Integer(); + for(const auto & t : json["allowedTerrains"].Vector()) + { + VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){ + info->allowedTerrains.emplace_back(identifier); + }); + } + for(const auto & t : json["specialBattlefields"].Vector()) + + info->allowedSpecialBfields.emplace_back(t.String()); + info->blockedTiles = json["blockedTiles"].convertTo>(); + info->isAbsoluteObstacle = json["absolute"].Bool(); + info->isForegroundObstacle = json["foreground"].Bool(); + + return info; +} + +std::vector ObstacleHandler::loadLegacyData() +{ + return {}; +} + +const std::vector & ObstacleHandler::getTypeNames() const +{ + static const std::vector types = { "obstacle" }; + return types; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/ObstacleHandler.h b/lib/ObstacleHandler.h index 7c81127a4..e80eb8dd6 100644 --- a/lib/ObstacleHandler.h +++ b/lib/ObstacleHandler.h @@ -1,79 +1,79 @@ -/* - * ObstacleHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include "GameConstants.h" -#include "IHandlerBase.h" -#include "battle/BattleHexArray.h" -#include "filesystem/ResourcePath.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class DLL_LINKAGE ObstacleInfo : public EntityT -{ -public: - ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false) - {} - - ObstacleInfo(Obstacle obstacle, std::string identifier) - : obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false) - { - } - - Obstacle obstacle; - si32 iconIndex; - std::string modScope; - std::string identifier; - AudioPath appearSound; - AnimationPath appearAnimation; - AnimationPath animation; - std::vector allowedTerrains; - std::vector allowedSpecialBfields; - - bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same - bool isForegroundObstacle; - si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) - si32 height; - std::vector blockedTiles; //offsets relative to obstacle position (that is its left bottom corner) - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - std::string getNameTranslated() const override; - std::string getNameTextID() const override; - void registerIcons(const IconRegistar & cb) const override; - Obstacle getId() const override; - - BattleHexArray getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' - - bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const; -}; - -class DLL_LINKAGE ObstacleService : public EntityServiceT -{ -public: -}; - -class ObstacleHandler: public CHandlerBase -{ -public: - std::shared_ptr loadFromJson(const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - const std::vector & getTypeNames() const override; - std::vector loadLegacyData() override; -}; - -VCMI_LIB_NAMESPACE_END +/* + * ObstacleHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include +#include +#include "GameConstants.h" +#include "IHandlerBase.h" +#include "battle/BattleHexArray.h" +#include "filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE ObstacleInfo : public EntityT +{ +public: + ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false) + {} + + ObstacleInfo(Obstacle obstacle, std::string identifier) + : obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false) + { + } + + Obstacle obstacle; + si32 iconIndex; + std::string modScope; + std::string identifier; + AudioPath appearSound; + AnimationPath appearAnimation; + AnimationPath animation; + std::vector allowedTerrains; + std::vector allowedSpecialBfields; + + bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same + bool isForegroundObstacle; + si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) + si32 height; + std::vector blockedTiles; //offsets relative to obstacle position (that is its left bottom corner) + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + std::string getNameTranslated() const override; + std::string getNameTextID() const override; + void registerIcons(const IconRegistar & cb) const override; + Obstacle getId() const override; + + BattleHexArray getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' + + bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const; +}; + +class DLL_LINKAGE ObstacleService : public EntityServiceT +{ +public: +}; + +class ObstacleHandler: public CHandlerBase +{ +public: + std::shared_ptr loadFromJson(const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index 197a0451c..7e21c4937 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -1,138 +1,138 @@ -/* - * BattleHex.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "BattleHex.h" - -VCMI_LIB_NAMESPACE_BEGIN - -BattleHex::BattleHex() : hex(INVALID) {} - -BattleHex::BattleHex(si16 _hex) : hex(_hex) {} - -BattleHex::BattleHex(si16 x, si16 y) -{ - setXY(x, y); -} - -BattleHex::BattleHex(std::pair xy) -{ - setXY(xy); -} - -BattleHex::operator si16() const -{ - return hex; -} - -void BattleHex::setX(si16 x) -{ - setXY(x, getY()); -} - -void BattleHex::setY(si16 y) -{ - setXY(getX(), y); -} - -void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) -{ - if(hasToBeValid) - { - if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) - throw std::runtime_error("Valid hex required"); - } - - hex = x + y * GameConstants::BFIELD_WIDTH; -} - -void BattleHex::setXY(std::pair xy) -{ - setXY(xy.first, xy.second); -} - -si16 BattleHex::getX() const -{ - return hex % GameConstants::BFIELD_WIDTH; -} - -si16 BattleHex::getY() const -{ - return hex / GameConstants::BFIELD_WIDTH; -} - -std::pair BattleHex::getXY() const -{ - return std::make_pair(getX(), getY()); -} - -BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid) -{ - si16 x = getX(); - si16 y = getY(); - switch(dir) - { - case TOP_LEFT: - setXY((y%2) ? x-1 : x, y-1, hasToBeValid); - break; - case TOP_RIGHT: - setXY((y%2) ? x : x+1, y-1, hasToBeValid); - break; - case RIGHT: - setXY(x+1, y, hasToBeValid); - break; - case BOTTOM_RIGHT: - setXY((y%2) ? x : x+1, y+1, hasToBeValid); - break; - case BOTTOM_LEFT: - setXY((y%2) ? x-1 : x, y+1, hasToBeValid); - break; - case LEFT: - setXY(x-1, y, hasToBeValid); - break; - case NONE: - break; - default: - throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); - break; - } - return *this; -} - -BattleHex & BattleHex::operator+=(BattleHex::EDir dir) -{ - return moveInDirection(dir); -} - -BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const -{ - BattleHex result(hex); - result.moveInDirection(dir, hasToBeValid); - return result; -} - -BattleHex BattleHex::operator+(BattleHex::EDir dir) const -{ - return cloneInDirection(dir); -} - -BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) -{ - for(auto dir : hexagonalDirections()) - if(hex2 == hex1.cloneInDirection(dir, false)) - return dir; - return NONE; -} - -std::ostream & operator<<(std::ostream & os, const BattleHex & hex) -{ - return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); -} - -VCMI_LIB_NAMESPACE_END +/* + * BattleHex.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "BattleHex.h" + +VCMI_LIB_NAMESPACE_BEGIN + +BattleHex::BattleHex() : hex(INVALID) {} + +BattleHex::BattleHex(si16 _hex) : hex(_hex) {} + +BattleHex::BattleHex(si16 x, si16 y) +{ + setXY(x, y); +} + +BattleHex::BattleHex(std::pair xy) +{ + setXY(xy); +} + +BattleHex::operator si16() const +{ + return hex; +} + +void BattleHex::setX(si16 x) +{ + setXY(x, getY()); +} + +void BattleHex::setY(si16 y) +{ + setXY(getX(), y); +} + +void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) +{ + if(hasToBeValid) + { + if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) + throw std::runtime_error("Valid hex required"); + } + + hex = x + y * GameConstants::BFIELD_WIDTH; +} + +void BattleHex::setXY(std::pair xy) +{ + setXY(xy.first, xy.second); +} + +si16 BattleHex::getX() const +{ + return hex % GameConstants::BFIELD_WIDTH; +} + +si16 BattleHex::getY() const +{ + return hex / GameConstants::BFIELD_WIDTH; +} + +std::pair BattleHex::getXY() const +{ + return std::make_pair(getX(), getY()); +} + +BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid) +{ + si16 x = getX(); + si16 y = getY(); + switch(dir) + { + case TOP_LEFT: + setXY((y%2) ? x-1 : x, y-1, hasToBeValid); + break; + case TOP_RIGHT: + setXY((y%2) ? x : x+1, y-1, hasToBeValid); + break; + case RIGHT: + setXY(x+1, y, hasToBeValid); + break; + case BOTTOM_RIGHT: + setXY((y%2) ? x : x+1, y+1, hasToBeValid); + break; + case BOTTOM_LEFT: + setXY((y%2) ? x-1 : x, y+1, hasToBeValid); + break; + case LEFT: + setXY(x-1, y, hasToBeValid); + break; + case NONE: + break; + default: + throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); + break; + } + return *this; +} + +BattleHex & BattleHex::operator+=(BattleHex::EDir dir) +{ + return moveInDirection(dir); +} + +BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const +{ + BattleHex result(hex); + result.moveInDirection(dir, hasToBeValid); + return result; +} + +BattleHex BattleHex::operator+(BattleHex::EDir dir) const +{ + return cloneInDirection(dir); +} + +BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) +{ + for(auto dir : hexagonalDirections()) + if(hex2 == hex1.cloneInDirection(dir, false)) + return dir; + return NONE; +} + +std::ostream & operator<<(std::ostream & os, const BattleHex & hex) +{ + return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index e86a5caf1..4751d7515 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -1,127 +1,127 @@ -/* - * BattleHex.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "BattleSide.h" - -VCMI_LIB_NAMESPACE_BEGIN - -//TODO: change to enum class - -namespace GameConstants -{ - const int BFIELD_WIDTH = 17; - const int BFIELD_HEIGHT = 11; - const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; -} - -// for battle stacks' positions -struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design -{ - // helpers for siege - static constexpr si16 CASTLE_CENTRAL_TOWER = -2; - static constexpr si16 CASTLE_BOTTOM_TOWER = -3; - static constexpr si16 CASTLE_UPPER_TOWER = -4; - - // hexes for interaction with heroes - static constexpr si16 HERO_ATTACKER = 0; - static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1; - - // helpers for rendering - static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits::min(); - static constexpr si16 HEX_AFTER_ALL = std::numeric_limits::max(); - - static constexpr si16 DESTRUCTIBLE_WALL_1 = 29; - static constexpr si16 DESTRUCTIBLE_WALL_2 = 78; - static constexpr si16 DESTRUCTIBLE_WALL_3 = 130; - static constexpr si16 DESTRUCTIBLE_WALL_4 = 182; - static constexpr si16 GATE_BRIDGE = 94; - static constexpr si16 GATE_OUTER = 95; - static constexpr si16 GATE_INNER = 96; - - si16 hex; - static constexpr si16 INVALID = -1; - enum EDir - { - NONE = -1, - - TOP_LEFT, - TOP_RIGHT, - RIGHT, - BOTTOM_RIGHT, - BOTTOM_LEFT, - LEFT, - - //Note: unused by BattleHex class, used by other code - TOP, - BOTTOM - }; - - BattleHex(); - BattleHex(si16 _hex); - BattleHex(si16 x, si16 y); - BattleHex(std::pair xy); - operator si16() const; - inline bool isValid() const - { - return hex >= 0 && hex < GameConstants::BFIELD_SIZE; - } - - bool isAvailable() const //valid position not in first or last column - { - return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; - } - - void setX(si16 x); - void setY(si16 y); - void setXY(si16 x, si16 y, bool hasToBeValid = true); - void setXY(std::pair xy); - si16 getX() const; - si16 getY() const; - std::pair getXY() const; - BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); - BattleHex& operator+=(EDir dir); - BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const; - BattleHex operator+(EDir dir) const; - - static EDir mutualPosition(BattleHex hex1, BattleHex hex2); - static uint8_t getDistance(BattleHex hex1, BattleHex hex2) - { - int y1 = hex1.getY(); - int y2 = hex2.getY(); - - // FIXME: why there was * 0.5 instead of / 2? - int x1 = static_cast(hex1.getX() + y1 / 2); - int x2 = static_cast(hex2.getX() + y2 / 2); - - int xDst = x2 - x1; - int yDst = y2 - y1; - - if((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0)) - return std::max(std::abs(xDst), std::abs(yDst)); - - return std::abs(xDst) + std::abs(yDst); - } - - template - void serialize(Handler &h) - { - h & hex; - } - - //Constexpr defined array with all directions used in battle - static constexpr auto hexagonalDirections() { - return std::array{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; - } -}; - -DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); - -VCMI_LIB_NAMESPACE_END +/* + * BattleHex.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "BattleSide.h" + +VCMI_LIB_NAMESPACE_BEGIN + +//TODO: change to enum class + +namespace GameConstants +{ + const int BFIELD_WIDTH = 17; + const int BFIELD_HEIGHT = 11; + const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; +} + +// for battle stacks' positions +struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design +{ + // helpers for siege + static constexpr si16 CASTLE_CENTRAL_TOWER = -2; + static constexpr si16 CASTLE_BOTTOM_TOWER = -3; + static constexpr si16 CASTLE_UPPER_TOWER = -4; + + // hexes for interaction with heroes + static constexpr si16 HERO_ATTACKER = 0; + static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1; + + // helpers for rendering + static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits::min(); + static constexpr si16 HEX_AFTER_ALL = std::numeric_limits::max(); + + static constexpr si16 DESTRUCTIBLE_WALL_1 = 29; + static constexpr si16 DESTRUCTIBLE_WALL_2 = 78; + static constexpr si16 DESTRUCTIBLE_WALL_3 = 130; + static constexpr si16 DESTRUCTIBLE_WALL_4 = 182; + static constexpr si16 GATE_BRIDGE = 94; + static constexpr si16 GATE_OUTER = 95; + static constexpr si16 GATE_INNER = 96; + + si16 hex; + static constexpr si16 INVALID = -1; + enum EDir + { + NONE = -1, + + TOP_LEFT, + TOP_RIGHT, + RIGHT, + BOTTOM_RIGHT, + BOTTOM_LEFT, + LEFT, + + //Note: unused by BattleHex class, used by other code + TOP, + BOTTOM + }; + + BattleHex(); + BattleHex(si16 _hex); + BattleHex(si16 x, si16 y); + BattleHex(std::pair xy); + operator si16() const; + inline bool isValid() const + { + return hex >= 0 && hex < GameConstants::BFIELD_SIZE; + } + + bool isAvailable() const //valid position not in first or last column + { + return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; + } + + void setX(si16 x); + void setY(si16 y); + void setXY(si16 x, si16 y, bool hasToBeValid = true); + void setXY(std::pair xy); + si16 getX() const; + si16 getY() const; + std::pair getXY() const; + BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); + BattleHex& operator+=(EDir dir); + BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const; + BattleHex operator+(EDir dir) const; + + static EDir mutualPosition(BattleHex hex1, BattleHex hex2); + static uint8_t getDistance(BattleHex hex1, BattleHex hex2) + { + int y1 = hex1.getY(); + int y2 = hex2.getY(); + + // FIXME: why there was * 0.5 instead of / 2? + int x1 = static_cast(hex1.getX() + y1 / 2); + int x2 = static_cast(hex2.getX() + y2 / 2); + + int xDst = x2 - x1; + int yDst = y2 - y1; + + if((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0)) + return std::max(std::abs(xDst), std::abs(yDst)); + + return std::abs(xDst) + std::abs(yDst); + } + + template + void serialize(Handler &h) + { + h & hex; + } + + //Constexpr defined array with all directions used in battle + static constexpr auto hexagonalDirections() { + return std::array{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; + } +}; + +DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/Destination.h b/lib/battle/Destination.h index 4d321589d..25c7d9f97 100644 --- a/lib/battle/Destination.h +++ b/lib/battle/Destination.h @@ -1,43 +1,43 @@ -/* - * Destination.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "BattleHexArray.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace battle -{ - -class Unit; - -class DLL_LINKAGE Destination -{ -public: - Destination(); - ~Destination() = default; - explicit Destination(const Unit * destination); - explicit Destination(const BattleHex & destination); - explicit Destination(const Unit * destination, const BattleHex & exactHex); - - Destination(const Destination & other) = default; - - Destination & operator=(const Destination & other) = default; - - const Unit * unitValue; - BattleHex hexValue; -}; - -using Target = std::vector; - -} - -VCMI_LIB_NAMESPACE_END +/* + * Destination.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "BattleHexArray.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace battle +{ + +class Unit; + +class DLL_LINKAGE Destination +{ +public: + Destination(); + ~Destination() = default; + explicit Destination(const Unit * destination); + explicit Destination(const BattleHex & destination); + explicit Destination(const Unit * destination, const BattleHex & exactHex); + + Destination(const Destination & other) = default; + + Destination & operator=(const Destination & other) = default; + + const Unit * unitValue; + BattleHex hexValue; +}; + +using Target = std::vector; + +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index c858e6e9f..546eb9a7e 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -1,89 +1,89 @@ -/* - * IBattleInfoCallback.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "GameConstants.h" -#include "BattleHexArray.h" - -#include - -#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false) - -VCMI_LIB_NAMESPACE_BEGIN - -struct CObstacleInstance; -class BattleField; -class IBattleInfo; - -namespace battle -{ - class IUnitInfo; - class Unit; - using Units = std::vector; - using UnitFilter = std::function; -} - -struct DamageRange -{ - int64_t min = 0; - int64_t max = 0; -}; - -struct DamageEstimation -{ - DamageRange damage; - DamageRange kills; -}; - -#if SCRIPTING_ENABLED -namespace scripting -{ - class Pool; -} -#endif - -class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider -{ -public: -#if SCRIPTING_ENABLED - virtual scripting::Pool * getContextPool() const = 0; -#endif - virtual ~IBattleInfoCallback() = default; - - virtual const IBattleInfo * getBattle() const = 0; - virtual std::optional getPlayerID() const = 0; - - virtual TerrainId battleTerrainType() const = 0; - virtual BattleField battleGetBattlefieldType() const = 0; - - ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw - virtual std::optional battleIsFinished() const = 0; - - virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase - virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?) - - virtual uint32_t battleNextUnitId() const = 0; - - virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0; - - virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0; - virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0; - - virtual const battle::Unit * battleActiveUnit() const = 0; - - //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) - virtual std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0; - virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const = 0; -}; - - - -VCMI_LIB_NAMESPACE_END +/* + * IBattleInfoCallback.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "GameConstants.h" +#include "BattleHexArray.h" + +#include + +#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false) + +VCMI_LIB_NAMESPACE_BEGIN + +struct CObstacleInstance; +class BattleField; +class IBattleInfo; + +namespace battle +{ + class IUnitInfo; + class Unit; + using Units = std::vector; + using UnitFilter = std::function; +} + +struct DamageRange +{ + int64_t min = 0; + int64_t max = 0; +}; + +struct DamageEstimation +{ + DamageRange damage; + DamageRange kills; +}; + +#if SCRIPTING_ENABLED +namespace scripting +{ + class Pool; +} +#endif + +class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider +{ +public: +#if SCRIPTING_ENABLED + virtual scripting::Pool * getContextPool() const = 0; +#endif + virtual ~IBattleInfoCallback() = default; + + virtual const IBattleInfo * getBattle() const = 0; + virtual std::optional getPlayerID() const = 0; + + virtual TerrainId battleTerrainType() const = 0; + virtual BattleField battleGetBattlefieldType() const = 0; + + ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw + virtual std::optional battleIsFinished() const = 0; + + virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase + virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?) + + virtual uint32_t battleNextUnitId() const = 0; + + virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0; + + virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0; + virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0; + + virtual const battle::Unit * battleActiveUnit() const = 0; + + //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) + virtual std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0; + virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const = 0; +}; + + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index 826fa6626..e00393d4a 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -1,89 +1,89 @@ -/* - * ReachabilityInfo.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "ReachabilityInfo.h" -#include "Unit.h" - -VCMI_LIB_NAMESPACE_BEGIN - -ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition): - perspective(static_cast(Stack->unitSide())), - startPosition(StartPosition), - doubleWide(Stack->doubleWide()), - side(Stack->unitSide()), - flying(Stack->hasBonusOfType(BonusType::FLYING)) -{ - knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); -} - -ReachabilityInfo::ReachabilityInfo() -{ - distances.fill(INFINITE_DIST); - predecessors.fill(BattleHex::INVALID); -} - -bool ReachabilityInfo::isReachable(BattleHex hex) const -{ - return distances[hex] < INFINITE_DIST; -} - -uint32_t ReachabilityInfo::distToNearestNeighbour( - const BattleHexArray & targetHexes, - BattleHex * chosenHex) const -{ - uint32_t ret = 1000000; - - for(auto targetHex : targetHexes) - { - for(auto & n : BattleHexArray::neighbouringTilesCache[targetHex]) - { - if(distances[n] < ret) - { - ret = distances[n]; - if(chosenHex) - *chosenHex = n; - } - } - } - - return ret; -} - -uint32_t ReachabilityInfo::distToNearestNeighbour( - const battle::Unit * attacker, - const battle::Unit * defender, - BattleHex * chosenHex) const -{ - auto attackableHexes = defender->getHexes(); - - if(attacker->doubleWide()) - { - if(defender->doubleWide()) - { - // It can be back to back attack o==o or head to head =oo=. - // In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles - attackableHexes.merge(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); - } - else - { - attackableHexes.merge(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); - } - } - - vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool - { - return h.getY() != defender->getPosition().getY() || !h.isAvailable(); - }); - - return distToNearestNeighbour(attackableHexes, chosenHex); -} - -VCMI_LIB_NAMESPACE_END +/* + * ReachabilityInfo.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "ReachabilityInfo.h" +#include "Unit.h" + +VCMI_LIB_NAMESPACE_BEGIN + +ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition): + perspective(static_cast(Stack->unitSide())), + startPosition(StartPosition), + doubleWide(Stack->doubleWide()), + side(Stack->unitSide()), + flying(Stack->hasBonusOfType(BonusType::FLYING)) +{ + knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); +} + +ReachabilityInfo::ReachabilityInfo() +{ + distances.fill(INFINITE_DIST); + predecessors.fill(BattleHex::INVALID); +} + +bool ReachabilityInfo::isReachable(BattleHex hex) const +{ + return distances[hex] < INFINITE_DIST; +} + +uint32_t ReachabilityInfo::distToNearestNeighbour( + const BattleHexArray & targetHexes, + BattleHex * chosenHex) const +{ + uint32_t ret = 1000000; + + for(auto targetHex : targetHexes) + { + for(auto & n : BattleHexArray::neighbouringTilesCache[targetHex]) + { + if(distances[n] < ret) + { + ret = distances[n]; + if(chosenHex) + *chosenHex = n; + } + } + } + + return ret; +} + +uint32_t ReachabilityInfo::distToNearestNeighbour( + const battle::Unit * attacker, + const battle::Unit * defender, + BattleHex * chosenHex) const +{ + auto attackableHexes = defender->getHexes(); + + if(attacker->doubleWide()) + { + if(defender->doubleWide()) + { + // It can be back to back attack o==o or head to head =oo=. + // In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles + attackableHexes.merge(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); + } + else + { + attackableHexes.merge(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); + } + } + + vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool + { + return h.getY() != defender->getPosition().getY() || !h.isAvailable(); + }); + + return distToNearestNeighbour(attackableHexes, chosenHex); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index f84b2de48..6243ec541 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -220,8 +220,8 @@ ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &contex return ILimiter::EDecision::DISCARD; auto accept = false; - for (const auto & hex : stack->getHexes()) - accept |= !!applicableHexes.count(hex); + for (auto hex : stack->getHexes()) + accept |= applicableHexes.contains(hex); return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; } @@ -236,7 +236,7 @@ JsonNode UnitOnHexLimiter::toJsonNode() const JsonNode root; root["type"].String() = "UNIT_ON_HEXES"; - for(const auto & hex : applicableHexes) + for(auto hex : applicableHexes) root["parameters"].Vector().emplace_back(hex); return root; diff --git a/lib/pathfinder/CGPathNode.h b/lib/pathfinder/CGPathNode.h index fd9f80d3c..2598633cc 100644 --- a/lib/pathfinder/CGPathNode.h +++ b/lib/pathfinder/CGPathNode.h @@ -1,243 +1,243 @@ -/* - * CGPathNode.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../GameConstants.h" -#include "../int3.h" - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -class CGHeroInstance; -class CGObjectInstance; -class CGameState; -class CPathfinderHelper; -struct TerrainTile; - -template -struct DLL_LINKAGE NodeComparer -{ - STRONG_INLINE - bool operator()(const N * lhs, const N * rhs) const - { - return lhs->getCost() > rhs->getCost(); - } -}; - -enum class EPathAccessibility : ui8 -{ - NOT_SET, - ACCESSIBLE, //tile can be entered and passed - VISITABLE, //tile can be entered as the last tile in path - GUARDED, //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any) - BLOCKVIS, //visitable from neighbouring tile but not passable - FLYABLE, //can only be accessed in air layer - BLOCKED //tile can be neither entered nor visited -}; - -enum class EPathNodeAction : ui8 -{ - UNKNOWN, - EMBARK, - DISEMBARK, - NORMAL, - BATTLE, - VISIT, - BLOCKING_VISIT, - TELEPORT_NORMAL, - TELEPORT_BLOCKING_VISIT, - TELEPORT_BATTLE -}; - -struct DLL_LINKAGE CGPathNode -{ - using TFibHeap = boost::heap::fibonacci_heap>>; - using ELayer = EPathfindingLayer; - - TFibHeap::handle_type pqHandle; - TFibHeap * pq; - CGPathNode * theNodeBefore; - - int3 coord; //coordinates - ELayer layer; - - float cost; //total cost of the path to this tile measured in turns with fractions - int moveRemains; //remaining movement points after hero reaches the tile - ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn - EPathAccessibility accessible; - EPathNodeAction action; - bool locked; - - CGPathNode() - : coord(-1), - layer(ELayer::WRONG), - pqHandle(nullptr) - { - reset(); - } - - STRONG_INLINE - void reset() - { - locked = false; - accessible = EPathAccessibility::NOT_SET; - moveRemains = 0; - cost = std::numeric_limits::max(); - turns = 255; - theNodeBefore = nullptr; - pq = nullptr; - action = EPathNodeAction::UNKNOWN; - } - - STRONG_INLINE - bool inPQ() const - { - return pq != nullptr; - } - - STRONG_INLINE - float getCost() const - { - return cost; - } - - STRONG_INLINE - void setCost(float value) - { - if(vstd::isAlmostEqual(value, cost)) - return; - - bool getUpNode = value < cost; - cost = value; - // If the node is in the heap, update the heap. - if(inPQ()) - { - if(getUpNode) - { - pq->increase(this->pqHandle); - } - else - { - pq->decrease(this->pqHandle); - } - } - } - - STRONG_INLINE - void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible) - { - if(layer == ELayer::WRONG) - { - coord = Coord; - layer = Layer; - } - else - { - reset(); - } - - accessible = Accessible; - } - - STRONG_INLINE - bool reachable() const - { - return turns < 255; - } - - bool isTeleportAction() const - { - if (action != EPathNodeAction::TELEPORT_NORMAL && - action != EPathNodeAction::TELEPORT_BLOCKING_VISIT && - action != EPathNodeAction::TELEPORT_BATTLE) - { - return false; - } - - return true; - } -}; - -struct DLL_LINKAGE CGPath -{ - std::vector nodes; //just get node by node - - /// Starting position of path, matches location of hero - const CGPathNode & currNode() const; - /// First node in path, this is where hero will move next - const CGPathNode & nextNode() const; - /// Last node in path, this is what hero wants to reach in the end - const CGPathNode & lastNode() const; - - int3 startPos() const; // start point - int3 endPos() const; //destination point -}; - -struct DLL_LINKAGE CPathsInfo -{ - using ELayer = EPathfindingLayer; - - const CGHeroInstance * hero; - int3 hpos; - int3 sizes; - boost::multi_array nodes; //[layer][level][w][h] - - CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); - ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 & tile) const; - bool getPath(CGPath & out, const int3 & dst) const; - const CGPathNode * getNode(const int3 & coord) const; - - STRONG_INLINE - CGPathNode * getNode(const int3 & coord, const ELayer layer) - { - return &nodes[layer.getNum()][coord.z][coord.x][coord.y]; - } -}; - -struct DLL_LINKAGE PathNodeInfo -{ - CGPathNode * node; - const CGObjectInstance * nodeObject; - const CGHeroInstance * nodeHero; - const TerrainTile * tile; - int3 coord; - bool guarded; - PlayerRelations objectRelations; - PlayerRelations heroRelations; - bool isInitialPosition; - - PathNodeInfo(); - - virtual void setNode(CGameState * gs, CGPathNode * n); - - void updateInfo(CPathfinderHelper * hlp, CGameState * gs); - - bool isNodeObjectVisitable() const; -}; - -struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo -{ - EPathNodeAction action; - int turn; - int movementLeft; - float cost; //same as CGPathNode::cost - bool blocked; - bool isGuardianTile; - - CDestinationNodeInfo(); - - void setNode(CGameState * gs, CGPathNode * n) override; - - virtual bool isBetterWay() const; -}; - -VCMI_LIB_NAMESPACE_END +/* + * CGPathNode.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../GameConstants.h" +#include "../int3.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +class CGHeroInstance; +class CGObjectInstance; +class CGameState; +class CPathfinderHelper; +struct TerrainTile; + +template +struct DLL_LINKAGE NodeComparer +{ + STRONG_INLINE + bool operator()(const N * lhs, const N * rhs) const + { + return lhs->getCost() > rhs->getCost(); + } +}; + +enum class EPathAccessibility : ui8 +{ + NOT_SET, + ACCESSIBLE, //tile can be entered and passed + VISITABLE, //tile can be entered as the last tile in path + GUARDED, //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any) + BLOCKVIS, //visitable from neighboring tile but not passable + FLYABLE, //can only be accessed in air layer + BLOCKED //tile can be neither entered nor visited +}; + +enum class EPathNodeAction : ui8 +{ + UNKNOWN, + EMBARK, + DISEMBARK, + NORMAL, + BATTLE, + VISIT, + BLOCKING_VISIT, + TELEPORT_NORMAL, + TELEPORT_BLOCKING_VISIT, + TELEPORT_BATTLE +}; + +struct DLL_LINKAGE CGPathNode +{ + using TFibHeap = boost::heap::fibonacci_heap>>; + using ELayer = EPathfindingLayer; + + TFibHeap::handle_type pqHandle; + TFibHeap * pq; + CGPathNode * theNodeBefore; + + int3 coord; //coordinates + ELayer layer; + + float cost; //total cost of the path to this tile measured in turns with fractions + int moveRemains; //remaining movement points after hero reaches the tile + ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn + EPathAccessibility accessible; + EPathNodeAction action; + bool locked; + + CGPathNode() + : coord(-1), + layer(ELayer::WRONG), + pqHandle(nullptr) + { + reset(); + } + + STRONG_INLINE + void reset() + { + locked = false; + accessible = EPathAccessibility::NOT_SET; + moveRemains = 0; + cost = std::numeric_limits::max(); + turns = 255; + theNodeBefore = nullptr; + pq = nullptr; + action = EPathNodeAction::UNKNOWN; + } + + STRONG_INLINE + bool inPQ() const + { + return pq != nullptr; + } + + STRONG_INLINE + float getCost() const + { + return cost; + } + + STRONG_INLINE + void setCost(float value) + { + if(vstd::isAlmostEqual(value, cost)) + return; + + bool getUpNode = value < cost; + cost = value; + // If the node is in the heap, update the heap. + if(inPQ()) + { + if(getUpNode) + { + pq->increase(this->pqHandle); + } + else + { + pq->decrease(this->pqHandle); + } + } + } + + STRONG_INLINE + void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible) + { + if(layer == ELayer::WRONG) + { + coord = Coord; + layer = Layer; + } + else + { + reset(); + } + + accessible = Accessible; + } + + STRONG_INLINE + bool reachable() const + { + return turns < 255; + } + + bool isTeleportAction() const + { + if (action != EPathNodeAction::TELEPORT_NORMAL && + action != EPathNodeAction::TELEPORT_BLOCKING_VISIT && + action != EPathNodeAction::TELEPORT_BATTLE) + { + return false; + } + + return true; + } +}; + +struct DLL_LINKAGE CGPath +{ + std::vector nodes; //just get node by node + + /// Starting position of path, matches location of hero + const CGPathNode & currNode() const; + /// First node in path, this is where hero will move next + const CGPathNode & nextNode() const; + /// Last node in path, this is what hero wants to reach in the end + const CGPathNode & lastNode() const; + + int3 startPos() const; // start point + int3 endPos() const; //destination point +}; + +struct DLL_LINKAGE CPathsInfo +{ + using ELayer = EPathfindingLayer; + + const CGHeroInstance * hero; + int3 hpos; + int3 sizes; + boost::multi_array nodes; //[layer][level][w][h] + + CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); + ~CPathsInfo(); + const CGPathNode * getPathInfo(const int3 & tile) const; + bool getPath(CGPath & out, const int3 & dst) const; + const CGPathNode * getNode(const int3 & coord) const; + + STRONG_INLINE + CGPathNode * getNode(const int3 & coord, const ELayer layer) + { + return &nodes[layer.getNum()][coord.z][coord.x][coord.y]; + } +}; + +struct DLL_LINKAGE PathNodeInfo +{ + CGPathNode * node; + const CGObjectInstance * nodeObject; + const CGHeroInstance * nodeHero; + const TerrainTile * tile; + int3 coord; + bool guarded; + PlayerRelations objectRelations; + PlayerRelations heroRelations; + bool isInitialPosition; + + PathNodeInfo(); + + virtual void setNode(CGameState * gs, CGPathNode * n); + + void updateInfo(CPathfinderHelper * hlp, CGameState * gs); + + bool isNodeObjectVisitable() const; +}; + +struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo +{ + EPathNodeAction action; + int turn; + int movementLeft; + float cost; //same as CGPathNode::cost + bool blocked; + bool isGuardianTile; + + CDestinationNodeInfo(); + + void setNode(CGameState * gs, CGPathNode * n) override; + + virtual bool isBetterWay() const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 2ae643a59..29b1acfc6 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -1,1027 +1,1027 @@ -/* - * CZonePlacer.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "CZonePlacer.h" - -#include "../TerrainHandler.h" -#include "../entities/faction/CFaction.h" -#include "../entities/faction/CTownHandler.h" -#include "../mapping/CMap.h" -#include "../mapping/CMapEditManager.h" -#include "../VCMI_Lib.h" -#include "CMapGenOptions.h" -#include "RmgMap.h" -#include "Zone.h" -#include "Functions.h" -#include "PenroseTiling.h" - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -//#define ZONE_PLACEMENT_LOG true - -CZonePlacer::CZonePlacer(RmgMap & map) - : width(0), height(0), mapSize(0), - gravityConstant(1e-3f), - stiffnessConstant(3e-3f), - stifness(0), - stiffnessIncreaseFactor(1.03f), - bestTotalDistance(1e10), - bestTotalOverlap(1e10), - map(map) -{ -} - -int3 CZonePlacer::cords(const float3 & f) const -{ - return int3(static_cast(std::max(0.f, (f.x * map.width()) - 1)), static_cast(std::max(0.f, (f.y * map.height() - 1))), f.z); -} - -float CZonePlacer::getDistance (float distance) const -{ - return (distance ? distance * distance : 1e-6f); -} - -void CZonePlacer::findPathsBetweenZones() -{ - auto zones = map.getZones(); - - std::set> zonesToCheck; - - // Iterate through each pair of nodes in the graph - - for (const auto& zone : zones) - { - int start = zone.first; - distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 - - std::queue q; - std::map visited; - visited[start] = true; - q.push(start); - - // Perform Breadth-First Search from the starting node - while (!q.empty()) - { - int current = q.front(); - q.pop(); - - const auto& currentZone = zones.at(current); - const auto& connectedZoneIds = currentZone->getConnections(); - - for (auto & connection : connectedZoneIds) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - auto neighbour = connection.getOtherZoneId(current); - - if (current == neighbour) - { - //Do not consider self-connections - continue; - } - - if (!visited[neighbour]) - { - visited[neighbour] = true; - q.push(neighbour); - distancesBetweenZones[start][neighbour] = distancesBetweenZones[start][current] + 1; - } - } - } - } -} - -void CZonePlacer::placeOnGrid(vstd::RNG* rand) -{ - auto zones = map.getZones(); - assert(zones.size()); - - //Make sure there are at least as many grid fields as the number of zones - size_t gridSize = std::ceil(std::sqrt(zones.size())); - - typedef boost::multi_array, 2> GridType; - GridType grid(boost::extents[gridSize][gridSize]); - - TZoneVector zonesVector(zones.begin(), zones.end()); - - //Place first zone - - auto firstZone = zonesVector[0].second; - size_t x = 0; - size_t y = 0; - - auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y) - { - switch (rand->nextInt(0, 3) % 4) - { - case 0: - x = 0; - y = gridSize / 2; - break; - case 1: - x = gridSize - 1; - y = gridSize / 2; - break; - case 2: - x = gridSize / 2; - y = 0; - break; - case 3: - x = gridSize / 2; - y = gridSize - 1; - break; - } - }; - - switch (firstZone->getType()) - { - case ETemplateZoneType::PLAYER_START: - case ETemplateZoneType::CPU_START: - if (firstZone->getConnectedZoneIds().size() > 2) - { - getRandomEdge(x, y); - } - else - { - //Random corner - if (rand->nextInt(0, 1) == 1) - { - x = 0; - } - else - { - x = gridSize - 1; - } - if (rand->nextInt(0, 1) == 1) - { - y = 0; - } - else - { - y = gridSize - 1; - } - } - break; - case ETemplateZoneType::TREASURE: - if (gridSize & 1) //odd - { - x = y = (gridSize / 2); - } - else - { - //One of 4 squares in the middle - x = (gridSize / 2) - 1 + rand->nextInt(0, 1); - y = (gridSize / 2) - 1 + rand->nextInt(0, 1); - } - break; - case ETemplateZoneType::JUNCTION: - getRandomEdge(x, y); - break; - } - grid[x][y] = firstZone; - - //Ignore z placement for simplicity - - for (size_t i = 1; i < zones.size(); i++) - { - auto zone = zonesVector[i].second; - auto connectedZoneIds = zone->getConnectedZoneIds(); - - float maxDistance = -1000.0; - int3 mostDistantPlace; - - //Iterate over free positions - for (size_t freeX = 0; freeX < gridSize; ++freeX) - { - for (size_t freeY = 0; freeY < gridSize; ++freeY) - { - if (!grid[freeX][freeY]) - { - //There is free space left here - int3 potentialPos(freeX, freeY, 0); - - //Compute distance to every existing zone - - float distance = 0; - for (size_t existingX = 0; existingX < gridSize; ++existingX) - { - for (size_t existingY = 0; existingY < gridSize; ++existingY) - { - auto existingZone = grid[existingX][existingY]; - if (existingZone) - { - //There is already zone here - float localDistance = 0.0f; - - auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()]; - if (graphDistance > 1) - { - //No direct connection - localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance; - } - else - { - //Has direct connection - place as close as possible - localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0)); - } - - localDistance *= scaleForceBetweenZones(zone, existingZone); - - distance += localDistance; - } - } - } - if (distance > maxDistance) - { - maxDistance = distance; - mostDistantPlace = potentialPos; - } - } - } - } - - //Place in a free slot - grid[mostDistantPlace.x][mostDistantPlace.y] = zone; - } - - //TODO: toggle with a flag -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Initial zone grid:"); - for (size_t x = 0; x < gridSize; ++x) - { - std::string s; - for (size_t y = 0; y < gridSize; ++y) - { - if (grid[x][y]) - { - s += (boost::format("%3d ") % grid[x][y]->getId()).str(); - } - else - { - s += " -- "; - } - } - logGlobal->trace(s); - } -#endif - - //Set initial position for zones - random position in square centered around (x, y) - for (size_t x = 0; x < gridSize; ++x) - { - for (size_t y = 0; y < gridSize; ++y) - { - auto zone = grid[x][y]; - if (zone) - { - //i.e. for grid size 5 we get range (0.25 - 4.75) - auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f); - vstd::abetween(targetX, 0.5, gridSize - 0.5); - auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f); - vstd::abetween(targetY, 0.5, gridSize - 0.5); - - zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); - } - } - } -} - -float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const -{ - if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game - { - int firstPlayer = zoneA->getOwner().value(); - int secondPlayer = zoneB->getOwner().value(); - - //Players with lower indexes (especially 1 and 2) will be placed further apart - - return (1.0f + (2.0f / (firstPlayer * secondPlayer))); - } - else - { - return 1; - } -} - -void CZonePlacer::placeZones(vstd::RNG * rand) -{ - logGlobal->info("Starting zone placement"); - - width = map.getMapGenOptions().getWidth(); - height = map.getMapGenOptions().getHeight(); - - auto zones = map.getZones(); - vstd::erase_if(zones, [](const std::pair> & pr) - { - return pr.second->getType() == ETemplateZoneType::WATER; - }); - bool underground = map.getMapGenOptions().getHasTwoLevels(); - - findPathsBetweenZones(); - placeOnGrid(rand); - - /* - Fruchterman-Reingold algorithm - - Let's assume we try to fit N circular zones with radius = size on a map - Connected zones attract, intersecting zones and map boundaries push back - */ - - TZoneVector zonesVector(zones.begin(), zones.end()); - assert (zonesVector.size()); - - RandomGeneratorUtil::randomShuffle(zonesVector, *rand); - - //0. set zone sizes and surface / underground level - prepareZones(zones, zonesVector, underground, rand); - - std::map, float3> bestSolution; - - TForceVector forces; - TForceVector totalForces; // both attraction and pushback, overcomplicated? - TDistanceVector distances; - TDistanceVector overlaps; - - auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool - { - bool improvement = false; - - float totalDistance = 0; - float totalOverlap = 0; - for (const auto& zone : distances) //find most misplaced zone - { - totalDistance += zone.second; - float overlap = overlaps[zone.first]; - totalOverlap += overlap; - } - - //check fitness function - if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1)) - { - //multiplication is better for auto-scaling, but stops working if one factor is 0 - improvement = true; - } - - //Save best solution - if (improvement) - { - bestTotalDistance = totalDistance; - bestTotalOverlap = totalOverlap; - - for (const auto& zone : zones) - bestSolution[zone.second] = zone.second->getCenter(); - } - -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); -#endif - - return improvement; - }; - - //Start with low stiffness. Bigger graphs need more time and more flexibility - for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;) - { - //1. attract connected zones - attractConnectedZones(zones, forces, distances); - for(const auto & zone : forces) - { - zone.first->setCenter (zone.first->getCenter() + zone.second); - totalForces[zone.first] = zone.second; //override - } - - //2. separate overlapping zones - separateOverlappingZones(zones, forces, overlaps); - for(const auto & zone : forces) - { - zone.first->setCenter (zone.first->getCenter() + zone.second); - totalForces[zone.first] += zone.second; //accumulate - } - - bool improved = evaluateSolution(); - - if (!improved) - { - //3. now perform drastic movement of zone that is completely not linked - //TODO: Don't do this is fitness was improved - moveOneZone(zones, totalForces, distances, overlaps); - - improved |= evaluateSolution(); - } - - if (!improved) - { - //Only cool down if we didn't see any improvement - stifness *= stiffnessIncreaseFactor; - } - - } - - logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap); - for(const auto & zone : zones) //finalize zone positions - { - zone.second->setPos (cords (bestSolution[zone.second])); -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString()); -#endif - } -} - -void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) -{ - std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map - - int zonesOnLevel[2] = { 0, 0 }; - - //even distribution for surface / underground zones. Surface zones always have priority. - - TZoneVector zonesToPlace; - std::map levels; - - //first pass - determine fixed surface for zones - for(const auto & zone : zonesVector) - { - if (!underground) //this step is ignored - zonesToPlace.push_back(zone); - else //place players depending on their factions - { - if(std::optional owner = zone.second->getOwner()) - { - auto player = PlayerColor(*owner - 1); - auto playerSettings = map.getMapGenOptions().getPlayersSettings(); - FactionID faction = FactionID::RANDOM; - if (playerSettings.size() > player) - { - faction = std::next(playerSettings.begin(), player)->second.getStartingTown(); - } - else - { - logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first); - } - - if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized - zonesToPlace.push_back(zone); - else - { - auto & tt = (*VLC->townh)[faction]->nativeTerrain; - if(tt == ETerrainId::NONE) - { - //any / random - zonesToPlace.push_back(zone); - } - else - { - const auto & terrainType = VLC->terrainTypeHandler->getById(tt); - if(terrainType->isUnderground() && !terrainType->isSurface()) - { - //underground only - zonesOnLevel[1]++; - levels[zone.first] = 1; - } - else - { - //surface - zonesOnLevel[0]++; - levels[zone.first] = 0; - } - } - } - } - else //no starting zone or no underground altogether - { - zonesToPlace.push_back(zone); - } - } - } - for(const auto & zone : zonesToPlace) - { - if (underground) //only then consider underground zones - { - int level = 0; - if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones - level = 1; - else - level = 0; - - levels[zone.first] = level; - zonesOnLevel[level]++; - } - else - levels[zone.first] = 0; - } - - for(const auto & zone : zonesVector) - { - int level = levels[zone.first]; - totalSize[level] += (zone.second->getSize() * zone.second->getSize()); - float3 center = zone.second->getCenter(); - center.z = level; - zone.second->setCenter(center); - } - - /* - prescale zones - - formula: sum((prescaler*n)^2)*pi = WH - - prescaler = sqrt((WH)/(sum(n^2)*pi)) - */ - - std::vector prescaler = { 0, 0 }; - for (int i = 0; i < 2; i++) - prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); - mapSize = static_cast(sqrt(width * height)); - for(const auto & zone : zones) - { - zone.second->setSize(static_cast(zone.second->getSize() * prescaler[zone.second->getCenter().z])); - } -} - -void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const -{ - for(const auto & zone : zones) - { - float3 forceVector(0, 0, 0); - float3 pos = zone.second->getCenter(); - float totalDistance = 0; - - for (const auto & connection : zone.second->getConnections()) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - if (connection.getZoneA() == connection.getZoneB()) - { - //Do not consider self-connections - continue; - } - - auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; - float3 otherZoneCenter = otherZone->getCenter(); - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - - forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value - - //Attract zone centers always - - float minDistance = 0; - - if (pos.z != otherZoneCenter.z) - minDistance = 0; //zones on different levels can overlap completely - else - minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates - - if (distance > minDistance) - totalDistance += (distance - minDistance); - } - distances[zone.second] = totalDistance; - forceVector.z = 0; //operator - doesn't preserve z coordinate :/ - forces[zone.second] = forceVector; - } -} - -void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps) -{ - for(const auto & zone : zones) - { - float3 forceVector(0, 0, 0); - float3 pos = zone.second->getCenter(); - - float overlap = 0; - //separate overlapping zones - for(const auto & otherZone : zones) - { - float3 otherZoneCenter = otherZone.second->getCenter(); - //zones on different levels don't push away - if (zone == otherZone || pos.z != otherZoneCenter.z) - continue; - - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; - if (distance < minDistance) - { - float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; - //negative value - localForce *= scaleForceBetweenZones(zone.second, otherZone.second); - forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); - overlap += (minDistance - distance); //overlapping of small zones hurts us more - } - } - - //move zones away from boundaries - //do not scale boundary distance - zones tend to get squashed - float size = zone.second->getSize() / mapSize; - - auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y) - { - float3 boundary = float3(x, y, pos.z); - auto distance = static_cast(pos.dist2d(boundary)); - overlap += std::max(0, distance - size); //check if we're closer to map boundary than value of zone size - forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value - }; - if (pos.x < size) - { - pushAwayFromBoundary(0, pos.y); - } - if (pos.x > 1 - size) - { - pushAwayFromBoundary(1, pos.y); - } - if (pos.y < size) - { - pushAwayFromBoundary(pos.x, 0); - } - if (pos.y > 1 - size) - { - pushAwayFromBoundary(pos.x, 1); - } - - //Always move repulsive zones away, no matter their distance - //TODO: Consider z plane? - for (auto& connection : zone.second->getConnections()) - { - if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) - { - auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; - float3 otherZoneCenter = otherZone->getCenter(); - - //TODO: Roll into lambda? - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; - float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; - localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]); - forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone); - } - } - - overlaps[zone.second] = overlap; - forceVector.z = 0; //operator - doesn't preserve z coordinate :/ - forces[zone.second] = forceVector; - } -} - -void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps) -{ - //The more zones, the greater total distance expected - //Also, higher stiffness make expected movement lower - const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness); - - typedef std::pair> Misplacement; - std::vector misplacedZones; - - float totalDistance = 0; - float totalOverlap = 0; - for (const auto& zone : distances) //find most misplaced zone - { - if (vstd::contains(lastSwappedZones, zone.first->getId())) - { - continue; - } - totalDistance += zone.second; - float overlap = overlaps[zone.first]; - totalOverlap += overlap; - //if distance to actual movement is long, the zone is misplaced - float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); - if (ratio > maxDistanceMovementRatio) - { - misplacedZones.emplace_back(std::make_pair(ratio, zone.first)); - } - } - - if (misplacedZones.empty()) - return; - - boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs) - { - return lhs.first > rhs.first; //Largest displacement first - }); - -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); -#endif - - if (misplacedZones.size() >= 2) - { - //Swap 2 misplaced zones - - auto firstZone = misplacedZones.front().second; - std::shared_ptr secondZone; - std::set connectedZones; - for (const auto& connection : firstZone->getConnections()) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - if (connection.getZoneA() == connection.getZoneB()) - { - //Do not consider self-connections - continue; - } - connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); - } - - auto level = firstZone->getCenter().z; - for (size_t i = 1; i < misplacedZones.size(); i++) - { - //Only swap zones on the same level - //Don't swap zones that should be connected (Jebus) - - if (misplacedZones[i].second->getCenter().z == level && - !vstd::contains(connectedZones, misplacedZones[i].second->getId())) - { - secondZone = misplacedZones[i].second; - break; - } - } - if (secondZone) - { -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); -#endif - - auto firstCenter = firstZone->getCenter(); - auto secondCenter = secondZone->getCenter(); - firstZone->setCenter(secondCenter); - secondZone->setCenter(firstCenter); - - lastSwappedZones.insert(firstZone->getId()); - lastSwappedZones.insert(secondZone->getId()); - return; - } - } - lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next - - //find most distant zone that should be attracted and move inside it - std::shared_ptr targetZone; - auto misplacedZone = misplacedZones.front().second; - float3 ourCenter = misplacedZone->getCenter(); - - if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1))) - { - //Move one zone towards most distant zone to reduce distance - - float maxDistance = 0; - for (auto con : misplacedZone->getConnections()) - { - if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE) - { - continue; - } - - auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())]; - float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); - if (distance > maxDistance) - { - maxDistance = distance; - targetZone = otherZone; - } - } - if (targetZone) - { - float3 vec = targetZone->getCenter() - ourCenter; - float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); -#endif - - misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size - } - } - else - { - //Move misplaced zone away from overlapping zone - - float maxOverlap = 0; - for(const auto & otherZone : zones) - { - float3 otherZoneCenter = otherZone.second->getCenter(); - - if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) - continue; - - auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); - if (distance > maxOverlap) - { - maxOverlap = distance; - targetZone = otherZone.second; - } - } - if (targetZone) - { - float3 vec = ourCenter - targetZone->getCenter(); - float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); -#endif - - misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated - } - } - //Don't swap that zone in next iteration - lastSwappedZones.insert(misplacedZone->getId()); -} - -float CZonePlacer::metric (const int3 &A, const int3 &B) const -{ - return A.dist2dSQ(B); - -} - -void CZonePlacer::assignZones(vstd::RNG * rand) -{ - logGlobal->info("Starting zone colouring"); - - auto width = map.getMapGenOptions().getWidth(); - auto height = map.getMapGenOptions().getHeight(); - - - auto zones = map.getZones(); - vstd::erase_if(zones, [](const std::pair> & pr) - { - return pr.second->getType() == ETemplateZoneType::WATER; - }); - - using Dpair = std::pair, float>; - std::vector distances; - distances.reserve(zones.size()); - - //now place zones correctly and assign tiles to each zone - - auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool - { - //bigger zones have smaller distance - return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); - }; - - auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool - { - //bigger zones have smaller distance - return lhs.second < rhs.second; - }; - - auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr & zone) -> void - { - int3 total(0, 0, 0); - auto tiles = zone->area()->getTiles(); - for(const auto & tile : tiles) - { - total += tile; - } - int size = static_cast(tiles.size()); - assert(size); - auto newPos = int3(total.x / size, total.y / size, total.z / size); - zone->setPos(newPos); - zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z)); - }; - - int levels = map.levels(); - - // Find current center of mass for each zone. Move zone to that center to balance zones sizes - std::vector zonesOnLevel; - for(int level = 0; level < levels; level++) - { - zonesOnLevel.push_back(map.getZonesOnLevel(level)); - } - - int3 pos; - - for(pos.z = 0; pos.z < levels; pos.z++) - { - for(pos.x = 0; pos.x < width; pos.x++) - { - for(pos.y = 0; pos.y < height; pos.y++) - { - distances.clear(); - for(const auto & zone : zonesOnLevel[pos.z]) - { - distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); - } - boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone - } - } - } - - for(const auto & zone : zones) - { - if(zone.second->area()->empty()) - throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); - - moveZoneToCenterOfMass(zone.second); - } - - for(const auto & zone : zones) - zone.second->clearTiles(); //now populate them again - - PenroseTiling penrose; - for (int level = 0; level < levels; level++) - { - //Create different tiling for each level - - auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand); - - // Assign zones to closest Penrose vertex - std::map, std::set> vertexMapping; - - for (const auto & vertex : vertices) - { - distances.clear(); - for(const auto & zone : zonesOnLevel[level]) - { - distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); - } - auto closestZone = boost::min_element(distances, compareByDistance)->first; - - vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone - } - - //Assign actual tiles to each zone - pos.z = level; - for (pos.x = 0; pos.x < width; pos.x++) - { - for (pos.y = 0; pos.y < height; pos.y++) - { - distances.clear(); - for(const auto & zoneVertex : vertexMapping) - { - auto zone = zoneVertex.first; - for (const auto & vertex : zoneVertex.second) - { - distances.emplace_back(zone, metric(pos, vertex)); - } - } - - //Tile closest to vertex belongs to zone - auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; - closestZone->area()->add(pos); - map.setZoneID(pos, closestZone->getId()); - } - } - - for(const auto & zone : zonesOnLevel[level]) - { - if(zone.second->area()->empty()) - { - // FIXME: Some vertices are duplicated, but it's not a source of problem - logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); - for (const auto & vertex : vertices) - { - logGlobal->warn("Penrose Vertex: %s", vertex.toString()); - } - throw rmgException("Empty zone after Penrose tiling"); - } - } - } - - //set position (town position) to center of mass of irregular zone - for(const auto & zone : zones) - { - moveZoneToCenterOfMass(zone.second); - - //TODO: similar for islands - #define CREATE_FULL_UNDERGROUND true //consider linking this with water amount - if (zone.second->isUnderground()) - { - if (!CREATE_FULL_UNDERGROUND) - { - auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f); - for(const auto & t : discardTiles) - zone.second->area()->erase(t); - } - - //make sure that terrain inside zone is not a rock - - auto v = zone.second->area()->getTilesVector(); - map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN); - } - } - logGlobal->info("Finished zone colouring"); -} - -const TDistanceMap& CZonePlacer::getDistanceMap() -{ - return distancesBetweenZones; -} - -VCMI_LIB_NAMESPACE_END +/* + * CZonePlacer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CZonePlacer.h" + +#include "../TerrainHandler.h" +#include "../entities/faction/CFaction.h" +#include "../entities/faction/CTownHandler.h" +#include "../mapping/CMap.h" +#include "../mapping/CMapEditManager.h" +#include "../VCMI_Lib.h" +#include "CMapGenOptions.h" +#include "RmgMap.h" +#include "Zone.h" +#include "Functions.h" +#include "PenroseTiling.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +//#define ZONE_PLACEMENT_LOG true + +CZonePlacer::CZonePlacer(RmgMap & map) + : width(0), height(0), mapSize(0), + gravityConstant(1e-3f), + stiffnessConstant(3e-3f), + stifness(0), + stiffnessIncreaseFactor(1.03f), + bestTotalDistance(1e10), + bestTotalOverlap(1e10), + map(map) +{ +} + +int3 CZonePlacer::cords(const float3 & f) const +{ + return int3(static_cast(std::max(0.f, (f.x * map.width()) - 1)), static_cast(std::max(0.f, (f.y * map.height() - 1))), f.z); +} + +float CZonePlacer::getDistance (float distance) const +{ + return (distance ? distance * distance : 1e-6f); +} + +void CZonePlacer::findPathsBetweenZones() +{ + auto zones = map.getZones(); + + std::set> zonesToCheck; + + // Iterate through each pair of nodes in the graph + + for (const auto& zone : zones) + { + int start = zone.first; + distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 + + std::queue q; + std::map visited; + visited[start] = true; + q.push(start); + + // Perform Breadth-First Search from the starting node + while (!q.empty()) + { + int current = q.front(); + q.pop(); + + const auto& currentZone = zones.at(current); + const auto& connectedZoneIds = currentZone->getConnections(); + + for (auto & connection : connectedZoneIds) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + auto neighbor = connection.getOtherZoneId(current); + + if (current == neighbor) + { + //Do not consider self-connections + continue; + } + + if (!visited[neighbor]) + { + visited[neighbor] = true; + q.push(neighbor); + distancesBetweenZones[start][neighbor] = distancesBetweenZones[start][current] + 1; + } + } + } + } +} + +void CZonePlacer::placeOnGrid(vstd::RNG* rand) +{ + auto zones = map.getZones(); + assert(zones.size()); + + //Make sure there are at least as many grid fields as the number of zones + size_t gridSize = std::ceil(std::sqrt(zones.size())); + + typedef boost::multi_array, 2> GridType; + GridType grid(boost::extents[gridSize][gridSize]); + + TZoneVector zonesVector(zones.begin(), zones.end()); + + //Place first zone + + auto firstZone = zonesVector[0].second; + size_t x = 0; + size_t y = 0; + + auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y) + { + switch (rand->nextInt(0, 3) % 4) + { + case 0: + x = 0; + y = gridSize / 2; + break; + case 1: + x = gridSize - 1; + y = gridSize / 2; + break; + case 2: + x = gridSize / 2; + y = 0; + break; + case 3: + x = gridSize / 2; + y = gridSize - 1; + break; + } + }; + + switch (firstZone->getType()) + { + case ETemplateZoneType::PLAYER_START: + case ETemplateZoneType::CPU_START: + if (firstZone->getConnectedZoneIds().size() > 2) + { + getRandomEdge(x, y); + } + else + { + //Random corner + if (rand->nextInt(0, 1) == 1) + { + x = 0; + } + else + { + x = gridSize - 1; + } + if (rand->nextInt(0, 1) == 1) + { + y = 0; + } + else + { + y = gridSize - 1; + } + } + break; + case ETemplateZoneType::TREASURE: + if (gridSize & 1) //odd + { + x = y = (gridSize / 2); + } + else + { + //One of 4 squares in the middle + x = (gridSize / 2) - 1 + rand->nextInt(0, 1); + y = (gridSize / 2) - 1 + rand->nextInt(0, 1); + } + break; + case ETemplateZoneType::JUNCTION: + getRandomEdge(x, y); + break; + } + grid[x][y] = firstZone; + + //Ignore z placement for simplicity + + for (size_t i = 1; i < zones.size(); i++) + { + auto zone = zonesVector[i].second; + auto connectedZoneIds = zone->getConnectedZoneIds(); + + float maxDistance = -1000.0; + int3 mostDistantPlace; + + //Iterate over free positions + for (size_t freeX = 0; freeX < gridSize; ++freeX) + { + for (size_t freeY = 0; freeY < gridSize; ++freeY) + { + if (!grid[freeX][freeY]) + { + //There is free space left here + int3 potentialPos(freeX, freeY, 0); + + //Compute distance to every existing zone + + float distance = 0; + for (size_t existingX = 0; existingX < gridSize; ++existingX) + { + for (size_t existingY = 0; existingY < gridSize; ++existingY) + { + auto existingZone = grid[existingX][existingY]; + if (existingZone) + { + //There is already zone here + float localDistance = 0.0f; + + auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()]; + if (graphDistance > 1) + { + //No direct connection + localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance; + } + else + { + //Has direct connection - place as close as possible + localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0)); + } + + localDistance *= scaleForceBetweenZones(zone, existingZone); + + distance += localDistance; + } + } + } + if (distance > maxDistance) + { + maxDistance = distance; + mostDistantPlace = potentialPos; + } + } + } + } + + //Place in a free slot + grid[mostDistantPlace.x][mostDistantPlace.y] = zone; + } + + //TODO: toggle with a flag +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Initial zone grid:"); + for (size_t x = 0; x < gridSize; ++x) + { + std::string s; + for (size_t y = 0; y < gridSize; ++y) + { + if (grid[x][y]) + { + s += (boost::format("%3d ") % grid[x][y]->getId()).str(); + } + else + { + s += " -- "; + } + } + logGlobal->trace(s); + } +#endif + + //Set initial position for zones - random position in square centered around (x, y) + for (size_t x = 0; x < gridSize; ++x) + { + for (size_t y = 0; y < gridSize; ++y) + { + auto zone = grid[x][y]; + if (zone) + { + //i.e. for grid size 5 we get range (0.25 - 4.75) + auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f); + vstd::abetween(targetX, 0.5, gridSize - 0.5); + auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f); + vstd::abetween(targetY, 0.5, gridSize - 0.5); + + zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); + } + } + } +} + +float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const +{ + if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game + { + int firstPlayer = zoneA->getOwner().value(); + int secondPlayer = zoneB->getOwner().value(); + + //Players with lower indexes (especially 1 and 2) will be placed further apart + + return (1.0f + (2.0f / (firstPlayer * secondPlayer))); + } + else + { + return 1; + } +} + +void CZonePlacer::placeZones(vstd::RNG * rand) +{ + logGlobal->info("Starting zone placement"); + + width = map.getMapGenOptions().getWidth(); + height = map.getMapGenOptions().getHeight(); + + auto zones = map.getZones(); + vstd::erase_if(zones, [](const std::pair> & pr) + { + return pr.second->getType() == ETemplateZoneType::WATER; + }); + bool underground = map.getMapGenOptions().getHasTwoLevels(); + + findPathsBetweenZones(); + placeOnGrid(rand); + + /* + Fruchterman-Reingold algorithm + + Let's assume we try to fit N circular zones with radius = size on a map + Connected zones attract, intersecting zones and map boundaries push back + */ + + TZoneVector zonesVector(zones.begin(), zones.end()); + assert (zonesVector.size()); + + RandomGeneratorUtil::randomShuffle(zonesVector, *rand); + + //0. set zone sizes and surface / underground level + prepareZones(zones, zonesVector, underground, rand); + + std::map, float3> bestSolution; + + TForceVector forces; + TForceVector totalForces; // both attraction and pushback, overcomplicated? + TDistanceVector distances; + TDistanceVector overlaps; + + auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool + { + bool improvement = false; + + float totalDistance = 0; + float totalOverlap = 0; + for (const auto& zone : distances) //find most misplaced zone + { + totalDistance += zone.second; + float overlap = overlaps[zone.first]; + totalOverlap += overlap; + } + + //check fitness function + if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1)) + { + //multiplication is better for auto-scaling, but stops working if one factor is 0 + improvement = true; + } + + //Save best solution + if (improvement) + { + bestTotalDistance = totalDistance; + bestTotalOverlap = totalOverlap; + + for (const auto& zone : zones) + bestSolution[zone.second] = zone.second->getCenter(); + } + +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); +#endif + + return improvement; + }; + + //Start with low stiffness. Bigger graphs need more time and more flexibility + for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;) + { + //1. attract connected zones + attractConnectedZones(zones, forces, distances); + for(const auto & zone : forces) + { + zone.first->setCenter (zone.first->getCenter() + zone.second); + totalForces[zone.first] = zone.second; //override + } + + //2. separate overlapping zones + separateOverlappingZones(zones, forces, overlaps); + for(const auto & zone : forces) + { + zone.first->setCenter (zone.first->getCenter() + zone.second); + totalForces[zone.first] += zone.second; //accumulate + } + + bool improved = evaluateSolution(); + + if (!improved) + { + //3. now perform drastic movement of zone that is completely not linked + //TODO: Don't do this is fitness was improved + moveOneZone(zones, totalForces, distances, overlaps); + + improved |= evaluateSolution(); + } + + if (!improved) + { + //Only cool down if we didn't see any improvement + stifness *= stiffnessIncreaseFactor; + } + + } + + logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap); + for(const auto & zone : zones) //finalize zone positions + { + zone.second->setPos (cords (bestSolution[zone.second])); +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString()); +#endif + } +} + +void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) +{ + std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map + + int zonesOnLevel[2] = { 0, 0 }; + + //even distribution for surface / underground zones. Surface zones always have priority. + + TZoneVector zonesToPlace; + std::map levels; + + //first pass - determine fixed surface for zones + for(const auto & zone : zonesVector) + { + if (!underground) //this step is ignored + zonesToPlace.push_back(zone); + else //place players depending on their factions + { + if(std::optional owner = zone.second->getOwner()) + { + auto player = PlayerColor(*owner - 1); + auto playerSettings = map.getMapGenOptions().getPlayersSettings(); + FactionID faction = FactionID::RANDOM; + if (playerSettings.size() > player) + { + faction = std::next(playerSettings.begin(), player)->second.getStartingTown(); + } + else + { + logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first); + } + + if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized + zonesToPlace.push_back(zone); + else + { + auto & tt = (*VLC->townh)[faction]->nativeTerrain; + if(tt == ETerrainId::NONE) + { + //any / random + zonesToPlace.push_back(zone); + } + else + { + const auto & terrainType = VLC->terrainTypeHandler->getById(tt); + if(terrainType->isUnderground() && !terrainType->isSurface()) + { + //underground only + zonesOnLevel[1]++; + levels[zone.first] = 1; + } + else + { + //surface + zonesOnLevel[0]++; + levels[zone.first] = 0; + } + } + } + } + else //no starting zone or no underground altogether + { + zonesToPlace.push_back(zone); + } + } + } + for(const auto & zone : zonesToPlace) + { + if (underground) //only then consider underground zones + { + int level = 0; + if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones + level = 1; + else + level = 0; + + levels[zone.first] = level; + zonesOnLevel[level]++; + } + else + levels[zone.first] = 0; + } + + for(const auto & zone : zonesVector) + { + int level = levels[zone.first]; + totalSize[level] += (zone.second->getSize() * zone.second->getSize()); + float3 center = zone.second->getCenter(); + center.z = level; + zone.second->setCenter(center); + } + + /* + prescale zones + + formula: sum((prescaler*n)^2)*pi = WH + + prescaler = sqrt((WH)/(sum(n^2)*pi)) + */ + + std::vector prescaler = { 0, 0 }; + for (int i = 0; i < 2; i++) + prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); + mapSize = static_cast(sqrt(width * height)); + for(const auto & zone : zones) + { + zone.second->setSize(static_cast(zone.second->getSize() * prescaler[zone.second->getCenter().z])); + } +} + +void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const +{ + for(const auto & zone : zones) + { + float3 forceVector(0, 0, 0); + float3 pos = zone.second->getCenter(); + float totalDistance = 0; + + for (const auto & connection : zone.second->getConnections()) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections + continue; + } + + auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; + float3 otherZoneCenter = otherZone->getCenter(); + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + + forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value + + //Attract zone centers always + + float minDistance = 0; + + if (pos.z != otherZoneCenter.z) + minDistance = 0; //zones on different levels can overlap completely + else + minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates + + if (distance > minDistance) + totalDistance += (distance - minDistance); + } + distances[zone.second] = totalDistance; + forceVector.z = 0; //operator - doesn't preserve z coordinate :/ + forces[zone.second] = forceVector; + } +} + +void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps) +{ + for(const auto & zone : zones) + { + float3 forceVector(0, 0, 0); + float3 pos = zone.second->getCenter(); + + float overlap = 0; + //separate overlapping zones + for(const auto & otherZone : zones) + { + float3 otherZoneCenter = otherZone.second->getCenter(); + //zones on different levels don't push away + if (zone == otherZone || pos.z != otherZoneCenter.z) + continue; + + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; + if (distance < minDistance) + { + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; + //negative value + localForce *= scaleForceBetweenZones(zone.second, otherZone.second); + forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); + overlap += (minDistance - distance); //overlapping of small zones hurts us more + } + } + + //move zones away from boundaries + //do not scale boundary distance - zones tend to get squashed + float size = zone.second->getSize() / mapSize; + + auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y) + { + float3 boundary = float3(x, y, pos.z); + auto distance = static_cast(pos.dist2d(boundary)); + overlap += std::max(0, distance - size); //check if we're closer to map boundary than value of zone size + forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value + }; + if (pos.x < size) + { + pushAwayFromBoundary(0, pos.y); + } + if (pos.x > 1 - size) + { + pushAwayFromBoundary(1, pos.y); + } + if (pos.y < size) + { + pushAwayFromBoundary(pos.x, 0); + } + if (pos.y > 1 - size) + { + pushAwayFromBoundary(pos.x, 1); + } + + //Always move repulsive zones away, no matter their distance + //TODO: Consider z plane? + for (auto& connection : zone.second->getConnections()) + { + if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) + { + auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; + float3 otherZoneCenter = otherZone->getCenter(); + + //TODO: Roll into lambda? + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; + localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]); + forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone); + } + } + + overlaps[zone.second] = overlap; + forceVector.z = 0; //operator - doesn't preserve z coordinate :/ + forces[zone.second] = forceVector; + } +} + +void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps) +{ + //The more zones, the greater total distance expected + //Also, higher stiffness make expected movement lower + const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness); + + typedef std::pair> Misplacement; + std::vector misplacedZones; + + float totalDistance = 0; + float totalOverlap = 0; + for (const auto& zone : distances) //find most misplaced zone + { + if (vstd::contains(lastSwappedZones, zone.first->getId())) + { + continue; + } + totalDistance += zone.second; + float overlap = overlaps[zone.first]; + totalOverlap += overlap; + //if distance to actual movement is long, the zone is misplaced + float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); + if (ratio > maxDistanceMovementRatio) + { + misplacedZones.emplace_back(std::make_pair(ratio, zone.first)); + } + } + + if (misplacedZones.empty()) + return; + + boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs) + { + return lhs.first > rhs.first; //Largest displacement first + }); + +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); +#endif + + if (misplacedZones.size() >= 2) + { + //Swap 2 misplaced zones + + auto firstZone = misplacedZones.front().second; + std::shared_ptr secondZone; + std::set connectedZones; + for (const auto& connection : firstZone->getConnections()) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections + continue; + } + connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); + } + + auto level = firstZone->getCenter().z; + for (size_t i = 1; i < misplacedZones.size(); i++) + { + //Only swap zones on the same level + //Don't swap zones that should be connected (Jebus) + + if (misplacedZones[i].second->getCenter().z == level && + !vstd::contains(connectedZones, misplacedZones[i].second->getId())) + { + secondZone = misplacedZones[i].second; + break; + } + } + if (secondZone) + { +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); +#endif + + auto firstCenter = firstZone->getCenter(); + auto secondCenter = secondZone->getCenter(); + firstZone->setCenter(secondCenter); + secondZone->setCenter(firstCenter); + + lastSwappedZones.insert(firstZone->getId()); + lastSwappedZones.insert(secondZone->getId()); + return; + } + } + lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next + + //find most distant zone that should be attracted and move inside it + std::shared_ptr targetZone; + auto misplacedZone = misplacedZones.front().second; + float3 ourCenter = misplacedZone->getCenter(); + + if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1))) + { + //Move one zone towards most distant zone to reduce distance + + float maxDistance = 0; + for (auto con : misplacedZone->getConnections()) + { + if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE) + { + continue; + } + + auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())]; + float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); + if (distance > maxDistance) + { + maxDistance = distance; + targetZone = otherZone; + } + } + if (targetZone) + { + float3 vec = targetZone->getCenter() - ourCenter; + float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif + + misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size + } + } + else + { + //Move misplaced zone away from overlapping zone + + float maxOverlap = 0; + for(const auto & otherZone : zones) + { + float3 otherZoneCenter = otherZone.second->getCenter(); + + if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) + continue; + + auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); + if (distance > maxOverlap) + { + maxOverlap = distance; + targetZone = otherZone.second; + } + } + if (targetZone) + { + float3 vec = ourCenter - targetZone->getCenter(); + float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif + + misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated + } + } + //Don't swap that zone in next iteration + lastSwappedZones.insert(misplacedZone->getId()); +} + +float CZonePlacer::metric (const int3 &A, const int3 &B) const +{ + return A.dist2dSQ(B); + +} + +void CZonePlacer::assignZones(vstd::RNG * rand) +{ + logGlobal->info("Starting zone colouring"); + + auto width = map.getMapGenOptions().getWidth(); + auto height = map.getMapGenOptions().getHeight(); + + + auto zones = map.getZones(); + vstd::erase_if(zones, [](const std::pair> & pr) + { + return pr.second->getType() == ETemplateZoneType::WATER; + }); + + using Dpair = std::pair, float>; + std::vector distances; + distances.reserve(zones.size()); + + //now place zones correctly and assign tiles to each zone + + auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool + { + //bigger zones have smaller distance + return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); + }; + + auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool + { + //bigger zones have smaller distance + return lhs.second < rhs.second; + }; + + auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr & zone) -> void + { + int3 total(0, 0, 0); + auto tiles = zone->area()->getTiles(); + for(const auto & tile : tiles) + { + total += tile; + } + int size = static_cast(tiles.size()); + assert(size); + auto newPos = int3(total.x / size, total.y / size, total.z / size); + zone->setPos(newPos); + zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z)); + }; + + int levels = map.levels(); + + // Find current center of mass for each zone. Move zone to that center to balance zones sizes + std::vector zonesOnLevel; + for(int level = 0; level < levels; level++) + { + zonesOnLevel.push_back(map.getZonesOnLevel(level)); + } + + int3 pos; + + for(pos.z = 0; pos.z < levels; pos.z++) + { + for(pos.x = 0; pos.x < width; pos.x++) + { + for(pos.y = 0; pos.y < height; pos.y++) + { + distances.clear(); + for(const auto & zone : zonesOnLevel[pos.z]) + { + distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); + } + boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone + } + } + } + + for(const auto & zone : zones) + { + if(zone.second->area()->empty()) + throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); + + moveZoneToCenterOfMass(zone.second); + } + + for(const auto & zone : zones) + zone.second->clearTiles(); //now populate them again + + PenroseTiling penrose; + for (int level = 0; level < levels; level++) + { + //Create different tiling for each level + + auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand); + + // Assign zones to closest Penrose vertex + std::map, std::set> vertexMapping; + + for (const auto & vertex : vertices) + { + distances.clear(); + for(const auto & zone : zonesOnLevel[level]) + { + distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); + } + auto closestZone = boost::min_element(distances, compareByDistance)->first; + + vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone + } + + //Assign actual tiles to each zone + pos.z = level; + for (pos.x = 0; pos.x < width; pos.x++) + { + for (pos.y = 0; pos.y < height; pos.y++) + { + distances.clear(); + for(const auto & zoneVertex : vertexMapping) + { + auto zone = zoneVertex.first; + for (const auto & vertex : zoneVertex.second) + { + distances.emplace_back(zone, metric(pos, vertex)); + } + } + + //Tile closest to vertex belongs to zone + auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; + closestZone->area()->add(pos); + map.setZoneID(pos, closestZone->getId()); + } + } + + for(const auto & zone : zonesOnLevel[level]) + { + if(zone.second->area()->empty()) + { + // FIXME: Some vertices are duplicated, but it's not a source of problem + logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); + for (const auto & vertex : vertices) + { + logGlobal->warn("Penrose Vertex: %s", vertex.toString()); + } + throw rmgException("Empty zone after Penrose tiling"); + } + } + } + + //set position (town position) to center of mass of irregular zone + for(const auto & zone : zones) + { + moveZoneToCenterOfMass(zone.second); + + //TODO: similar for islands + #define CREATE_FULL_UNDERGROUND true //consider linking this with water amount + if (zone.second->isUnderground()) + { + if (!CREATE_FULL_UNDERGROUND) + { + auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f); + for(const auto & t : discardTiles) + zone.second->area()->erase(t); + } + + //make sure that terrain inside zone is not a rock + + auto v = zone.second->area()->getTilesVector(); + map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN); + } + } + logGlobal->info("Finished zone colouring"); +} + +const TDistanceMap& CZonePlacer::getDistanceMap() +{ + return distancesBetweenZones; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/RmgPath.cpp b/lib/rmg/RmgPath.cpp index b36628f98..e9ffdcc48 100644 --- a/lib/rmg/RmgPath.cpp +++ b/lib/rmg/RmgPath.cpp @@ -117,6 +117,7 @@ Path Path::search(const Tileset & dst, bool straight, std::function::max(); auto it = distances.find(pos); diff --git a/lib/rmg/modificators/ObstaclePlacer.cpp b/lib/rmg/modificators/ObstaclePlacer.cpp index 22c308daa..5447a256e 100644 --- a/lib/rmg/modificators/ObstaclePlacer.cpp +++ b/lib/rmg/modificators/ObstaclePlacer.cpp @@ -172,4 +172,4 @@ bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const return false; } -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 323bbf5ab..25c232ca5 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -13,7 +13,6 @@ #include "SerializerReflection.h" #include "ESerializationVersion.h" #include "../mapObjects/CGHeroInstance.h" -#include "../battle/BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN @@ -443,19 +442,6 @@ public: load(data[key]); } } - - //void load(BattleHexArray & data) - //{ - // uint32_t length = readAndCheckLength(); - // data.clear(); - // BattleHex hex; - // for(uint32_t i = 0; i < length; i++) - // { - // load(hex); - // data.insert(hex); - // } - //} - void load(std::string &data) { if (hasFeature(Version::COMPACT_STRING_SERIALIZATION)) diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 6f2936239..738cf17c1 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -15,7 +15,6 @@ #include "ESerializationVersion.h" #include "Serializeable.h" #include "../mapObjects/CArmedInstance.h" -#include "../battle/BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/BattleSpellMechanics.h b/lib/spells/BattleSpellMechanics.h index 2b4655cd3..860a1487c 100644 --- a/lib/spells/BattleSpellMechanics.h +++ b/lib/spells/BattleSpellMechanics.h @@ -1,86 +1,86 @@ -/* - * BattleSpellMechanics.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "ISpellMechanics.h" - -#include "effects/Effects.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleSpellCast; - -namespace spells -{ - -class BattleSpellMechanics : public BaseMechanics -{ -public: - BattleSpellMechanics(const IBattleCast * event, std::shared_ptr effects_, std::shared_ptr targetCondition_); - virtual ~BattleSpellMechanics(); - - // TODO: ??? (what's the difference compared to cast?) - void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override; - - /// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield - bool canBeCast(Problem & problem) const override; - - /// Returns false if spell can not be cast at specified target - bool canBeCastAt(const Target & target, Problem & problem) const override; - - // TODO: ??? (what's the difference compared to applyEffects?) - void cast(ServerCallback * server, const Target & target) override final; - // TODO: ??? (what's the difference compared to cast?) - void castEval(ServerCallback * server, const Target & target) override final; - - /// Returns list of affected stack using currently configured target - std::vector getAffectedStacks(const Target & target) const override final; - - /// Returns list of target types that can be targeted by spell - std::vector getTargetTypes() const override final; - - /// Returns vector of all possible destinations for specified aim type - /// index - ??? - /// current - ??? - std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final; - - /// Returns true if spell can be cast on unit - bool isReceptive(const battle::Unit * target) const override; - - /// Returns list of hexes that are affected by spell assuming cast at centralHex - BattleHexArray rangeInHexes(BattleHex centralHex) const override; - - const Spell * getSpell() const override; - - bool counteringSelector(const Bonus * bonus) const; - -private: - std::shared_ptr effects; - std::shared_ptr targetCondition; - - std::vector affectedUnits; - effects::Effects::EffectsToApply effectsToApply; - - void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); - - std::set collectTargets() const; - - void doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector); - - BattleHexArray spellRangeInHexes(BattleHex centralHex) const; - - Target transformSpellTarget(const Target & aimPoint) const; -}; - -} - - -VCMI_LIB_NAMESPACE_END +/* + * BattleSpellMechanics.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "ISpellMechanics.h" + +#include "effects/Effects.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleSpellCast; + +namespace spells +{ + +class BattleSpellMechanics : public BaseMechanics +{ +public: + BattleSpellMechanics(const IBattleCast * event, std::shared_ptr effects_, std::shared_ptr targetCondition_); + virtual ~BattleSpellMechanics(); + + // TODO: ??? (what's the difference compared to cast?) + void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override; + + /// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield + bool canBeCast(Problem & problem) const override; + + /// Returns false if spell can not be cast at specified target + bool canBeCastAt(const Target & target, Problem & problem) const override; + + // TODO: ??? (what's the difference compared to applyEffects?) + void cast(ServerCallback * server, const Target & target) override final; + // TODO: ??? (what's the difference compared to cast?) + void castEval(ServerCallback * server, const Target & target) override final; + + /// Returns list of affected stack using currently configured target + std::vector getAffectedStacks(const Target & target) const override final; + + /// Returns list of target types that can be targeted by spell + std::vector getTargetTypes() const override final; + + /// Returns vector of all possible destinations for specified aim type + /// index - ??? + /// current - ??? + std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final; + + /// Returns true if spell can be cast on unit + bool isReceptive(const battle::Unit * target) const override; + + /// Returns list of hexes that are affected by spell assuming cast at centralHex + BattleHexArray rangeInHexes(BattleHex centralHex) const override; + + const Spell * getSpell() const override; + + bool counteringSelector(const Bonus * bonus) const; + +private: + std::shared_ptr effects; + std::shared_ptr targetCondition; + + std::vector affectedUnits; + effects::Effects::EffectsToApply effectsToApply; + + void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); + + std::set collectTargets() const; + + void doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector); + + BattleHexArray spellRangeInHexes(BattleHex centralHex) const; + + Target transformSpellTarget(const Target & aimPoint) const; +}; + +} + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index 4fac17a2d..84db51277 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -1,368 +1,368 @@ -/* - * ISpellMechanics.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include -#include - -#include "../battle/Destination.h" -#include "../int3.h" -#include "../GameConstants.h" -#include "../bonuses/Bonus.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct Query; -class IBattleState; -class CreatureService; -class CMap; -class CGameInfoCallback; -class CBattleInfoCallback; -class JsonNode; -class CStack; -class CGObjectInstance; -class CGHeroInstance; - -namespace spells -{ -class Service; -} - -namespace vstd -{ - class RNG; -} - -#if SCRIPTING_ENABLED -namespace scripting -{ - class Service; -} -#endif - - -///callback to be provided by server -class DLL_LINKAGE SpellCastEnvironment : public ServerCallback -{ -public: - virtual ~SpellCastEnvironment() = default; - - virtual const CMap * getMap() const = 0; - virtual const CGameInfoCallback * getCb() const = 0; - - virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; - virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove - - virtual void genericQuery(Query * request, PlayerColor color, std::function)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented -}; - -namespace spells -{ - -class DLL_LINKAGE IBattleCast -{ -public: - using Value = int32_t; - using Value64 = int64_t; - - using OptionalValue = std::optional; - using OptionalValue64 = std::optional; - - virtual const CSpell * getSpell() const = 0; - virtual Mode getMode() const = 0; - virtual const Caster * getCaster() const = 0; - virtual const CBattleInfoCallback * getBattle() const = 0; - - virtual OptionalValue getSpellLevel() const = 0; - - virtual OptionalValue getEffectPower() const = 0; - virtual OptionalValue getEffectDuration() const = 0; - - virtual OptionalValue64 getEffectValue() const = 0; - - virtual boost::logic::tribool isSmart() const = 0; - virtual boost::logic::tribool isMassive() const = 0; -}; - -///all parameters of particular cast event -class DLL_LINKAGE BattleCast : public IBattleCast -{ -public: - boost::logic::tribool smart; - boost::logic::tribool massive; - - //normal constructor - BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_); - - //magic mirror constructor - BattleCast(const BattleCast & orig, const Caster * caster_); - - virtual ~BattleCast(); - - ///IBattleCast - const CSpell * getSpell() const override; - Mode getMode() const override; - const Caster * getCaster() const override; - const CBattleInfoCallback * getBattle() const override; - - OptionalValue getSpellLevel() const override; - - OptionalValue getEffectPower() const override; - OptionalValue getEffectDuration() const override; - - OptionalValue64 getEffectValue() const override; - - boost::logic::tribool isSmart() const override; - boost::logic::tribool isMassive() const override; - - void setSpellLevel(Value value); - - void setEffectPower(Value value); - void setEffectDuration(Value value); - - void setEffectValue(Value64 value); - - ///only apply effects to specified targets - void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const; - - ///normal cast - void cast(ServerCallback * server, Target target); - - ///cast evaluation - void castEval(ServerCallback * server, Target target); - - ///cast with silent check for permitted cast - bool castIfPossible(ServerCallback * server, Target target); - - std::vector findPotentialTargets(bool fast = false) const; - -private: - ///spell school level - OptionalValue magicSkillLevel; - - ///actual spell-power affecting effect values - OptionalValue effectPower; - ///actual spell-power affecting effect duration - OptionalValue effectDuration; - - ///for Archangel-like casting - OptionalValue64 effectValue; - - Mode mode; - const CSpell * spell; - const CBattleInfoCallback * cb; - const Caster * caster; -}; - -class DLL_LINKAGE ISpellMechanicsFactory -{ -public: - virtual ~ISpellMechanicsFactory(); - - virtual std::unique_ptr create(const IBattleCast * event) const = 0; - - static std::unique_ptr get(const CSpell * s); - -protected: - const CSpell * spell; - - ISpellMechanicsFactory(const CSpell * s); -}; - -class DLL_LINKAGE Mechanics -{ -public: - virtual ~Mechanics(); - - virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0; - virtual bool adaptGenericProblem(Problem & target) const = 0; - - virtual BattleHexArray rangeInHexes(BattleHex centralHex) const = 0; - virtual std::vector getAffectedStacks(const Target & target) const = 0; - - virtual bool canBeCast(Problem & problem) const = 0; - virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0; - - virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0; - - virtual void cast(ServerCallback * server, const Target & target) = 0; - - virtual void castEval(ServerCallback * server, const Target & target) = 0; - - virtual bool isReceptive(const battle::Unit * target) const = 0; - - virtual std::vector getTargetTypes() const = 0; - - virtual std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0; - - virtual const Spell * getSpell() const = 0; - - //Cast event facade - - virtual IBattleCast::Value getEffectLevel() const = 0; - virtual IBattleCast::Value getRangeLevel() const = 0; - - virtual IBattleCast::Value getEffectPower() const = 0; - virtual IBattleCast::Value getEffectDuration() const = 0; - - virtual IBattleCast::Value64 getEffectValue() const = 0; - - virtual PlayerColor getCasterColor() const = 0; - - //Spell facade - virtual int32_t getSpellIndex() const = 0; - virtual SpellID getSpellId() const = 0; - virtual std::string getSpellName() const = 0; - virtual int32_t getSpellLevel() const = 0; - - virtual bool isSmart() const = 0; - virtual bool isMassive() const = 0; - virtual bool alwaysHitFirstTarget() const = 0; - virtual bool requiresClearTiles() const = 0; - - virtual bool isNegativeSpell() const = 0; - virtual bool isPositiveSpell() const = 0; - virtual bool isMagicalEffect() const = 0; - - virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0; - virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0; - virtual int64_t applySpecificSpellBonus(int64_t value) const = 0; - virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0; - - //Battle facade - virtual bool ownerMatches(const battle::Unit * unit) const = 0; - virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0; - - //Global environment facade - virtual const CreatureService * creatures() const = 0; -#if SCRIPTING_ENABLED - virtual const scripting::Service * scripts() const = 0; -#endif - virtual const Service * spells() const = 0; - - virtual const CBattleInfoCallback * battle() const = 0; - - const Caster * caster; - - BattleSide casterSide; - -protected: - Mechanics(); -}; - -class DLL_LINKAGE BaseMechanics : public Mechanics -{ -public: - virtual ~BaseMechanics(); - - bool adaptProblem(ESpellCastProblem source, Problem & target) const override; - bool adaptGenericProblem(Problem & target) const override; - - int32_t getSpellIndex() const override; - SpellID getSpellId() const override; - std::string getSpellName() const override; - int32_t getSpellLevel() const override; - - IBattleCast::Value getEffectLevel() const override; - IBattleCast::Value getRangeLevel() const override; - - IBattleCast::Value getEffectPower() const override; - IBattleCast::Value getEffectDuration() const override; - - IBattleCast::Value64 getEffectValue() const override; - - PlayerColor getCasterColor() const override; - - bool isSmart() const override; - bool isMassive() const override; - bool requiresClearTiles() const override; - bool alwaysHitFirstTarget() const override; - - bool isNegativeSpell() const override; - bool isPositiveSpell() const override; - bool isMagicalEffect() const override; - - int64_t adjustEffectValue(const battle::Unit * target) const override; - int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override; - int64_t applySpecificSpellBonus(int64_t value) const override; - int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override; - - bool ownerMatches(const battle::Unit * unit) const override; - bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override; - - std::vector getTargetTypes() const override; - - const CreatureService * creatures() const override; -#if SCRIPTING_ENABLED - const scripting::Service * scripts() const override; -#endif - const Service * spells() const override; - - const CBattleInfoCallback * battle() const override; - -protected: - const CSpell * owner; - Mode mode; - - BaseMechanics(const IBattleCast * event); - -private: - IBattleCast::Value rangeLevel; - IBattleCast::Value effectLevel; - - ///actual spell-power affecting effect values - IBattleCast::Value effectPower; - ///actual spell-power affecting effect duration - IBattleCast::Value effectDuration; - - ///raw damage/heal amount - IBattleCast::Value64 effectValue; - - boost::logic::tribool smart; - boost::logic::tribool massive; - - const CBattleInfoCallback * cb; -}; - -class DLL_LINKAGE IReceptiveCheck -{ -public: - virtual ~IReceptiveCheck() = default; - - virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0; -}; - -}// namespace spells - -class DLL_LINKAGE AdventureSpellCastParameters -{ -public: - const spells::Caster * caster; - int3 pos; -}; - -class DLL_LINKAGE IAdventureSpellMechanics -{ -public: - IAdventureSpellMechanics(const CSpell * s); - virtual ~IAdventureSpellMechanics() = default; - - virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0; - virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0; - - virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0; - - static std::unique_ptr createMechanics(const CSpell * s); -protected: - const CSpell * owner; -}; - -VCMI_LIB_NAMESPACE_END +/* + * ISpellMechanics.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include +#include + +#include "../battle/Destination.h" +#include "../int3.h" +#include "../GameConstants.h" +#include "../bonuses/Bonus.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct Query; +class IBattleState; +class CreatureService; +class CMap; +class CGameInfoCallback; +class CBattleInfoCallback; +class JsonNode; +class CStack; +class CGObjectInstance; +class CGHeroInstance; + +namespace spells +{ +class Service; +} + +namespace vstd +{ + class RNG; +} + +#if SCRIPTING_ENABLED +namespace scripting +{ + class Service; +} +#endif + + +///callback to be provided by server +class DLL_LINKAGE SpellCastEnvironment : public ServerCallback +{ +public: + virtual ~SpellCastEnvironment() = default; + + virtual const CMap * getMap() const = 0; + virtual const CGameInfoCallback * getCb() const = 0; + + virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; + virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove + + virtual void genericQuery(Query * request, PlayerColor color, std::function)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented +}; + +namespace spells +{ + +class DLL_LINKAGE IBattleCast +{ +public: + using Value = int32_t; + using Value64 = int64_t; + + using OptionalValue = std::optional; + using OptionalValue64 = std::optional; + + virtual const CSpell * getSpell() const = 0; + virtual Mode getMode() const = 0; + virtual const Caster * getCaster() const = 0; + virtual const CBattleInfoCallback * getBattle() const = 0; + + virtual OptionalValue getSpellLevel() const = 0; + + virtual OptionalValue getEffectPower() const = 0; + virtual OptionalValue getEffectDuration() const = 0; + + virtual OptionalValue64 getEffectValue() const = 0; + + virtual boost::logic::tribool isSmart() const = 0; + virtual boost::logic::tribool isMassive() const = 0; +}; + +///all parameters of particular cast event +class DLL_LINKAGE BattleCast : public IBattleCast +{ +public: + boost::logic::tribool smart; + boost::logic::tribool massive; + + //normal constructor + BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_); + + //magic mirror constructor + BattleCast(const BattleCast & orig, const Caster * caster_); + + virtual ~BattleCast(); + + ///IBattleCast + const CSpell * getSpell() const override; + Mode getMode() const override; + const Caster * getCaster() const override; + const CBattleInfoCallback * getBattle() const override; + + OptionalValue getSpellLevel() const override; + + OptionalValue getEffectPower() const override; + OptionalValue getEffectDuration() const override; + + OptionalValue64 getEffectValue() const override; + + boost::logic::tribool isSmart() const override; + boost::logic::tribool isMassive() const override; + + void setSpellLevel(Value value); + + void setEffectPower(Value value); + void setEffectDuration(Value value); + + void setEffectValue(Value64 value); + + ///only apply effects to specified targets + void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const; + + ///normal cast + void cast(ServerCallback * server, Target target); + + ///cast evaluation + void castEval(ServerCallback * server, Target target); + + ///cast with silent check for permitted cast + bool castIfPossible(ServerCallback * server, Target target); + + std::vector findPotentialTargets(bool fast = false) const; + +private: + ///spell school level + OptionalValue magicSkillLevel; + + ///actual spell-power affecting effect values + OptionalValue effectPower; + ///actual spell-power affecting effect duration + OptionalValue effectDuration; + + ///for Archangel-like casting + OptionalValue64 effectValue; + + Mode mode; + const CSpell * spell; + const CBattleInfoCallback * cb; + const Caster * caster; +}; + +class DLL_LINKAGE ISpellMechanicsFactory +{ +public: + virtual ~ISpellMechanicsFactory(); + + virtual std::unique_ptr create(const IBattleCast * event) const = 0; + + static std::unique_ptr get(const CSpell * s); + +protected: + const CSpell * spell; + + ISpellMechanicsFactory(const CSpell * s); +}; + +class DLL_LINKAGE Mechanics +{ +public: + virtual ~Mechanics(); + + virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0; + virtual bool adaptGenericProblem(Problem & target) const = 0; + + virtual BattleHexArray rangeInHexes(BattleHex centralHex) const = 0; + virtual std::vector getAffectedStacks(const Target & target) const = 0; + + virtual bool canBeCast(Problem & problem) const = 0; + virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0; + + virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0; + + virtual void cast(ServerCallback * server, const Target & target) = 0; + + virtual void castEval(ServerCallback * server, const Target & target) = 0; + + virtual bool isReceptive(const battle::Unit * target) const = 0; + + virtual std::vector getTargetTypes() const = 0; + + virtual std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0; + + virtual const Spell * getSpell() const = 0; + + //Cast event facade + + virtual IBattleCast::Value getEffectLevel() const = 0; + virtual IBattleCast::Value getRangeLevel() const = 0; + + virtual IBattleCast::Value getEffectPower() const = 0; + virtual IBattleCast::Value getEffectDuration() const = 0; + + virtual IBattleCast::Value64 getEffectValue() const = 0; + + virtual PlayerColor getCasterColor() const = 0; + + //Spell facade + virtual int32_t getSpellIndex() const = 0; + virtual SpellID getSpellId() const = 0; + virtual std::string getSpellName() const = 0; + virtual int32_t getSpellLevel() const = 0; + + virtual bool isSmart() const = 0; + virtual bool isMassive() const = 0; + virtual bool alwaysHitFirstTarget() const = 0; + virtual bool requiresClearTiles() const = 0; + + virtual bool isNegativeSpell() const = 0; + virtual bool isPositiveSpell() const = 0; + virtual bool isMagicalEffect() const = 0; + + virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0; + virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0; + virtual int64_t applySpecificSpellBonus(int64_t value) const = 0; + virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0; + + //Battle facade + virtual bool ownerMatches(const battle::Unit * unit) const = 0; + virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0; + + //Global environment facade + virtual const CreatureService * creatures() const = 0; +#if SCRIPTING_ENABLED + virtual const scripting::Service * scripts() const = 0; +#endif + virtual const Service * spells() const = 0; + + virtual const CBattleInfoCallback * battle() const = 0; + + const Caster * caster; + + BattleSide casterSide; + +protected: + Mechanics(); +}; + +class DLL_LINKAGE BaseMechanics : public Mechanics +{ +public: + virtual ~BaseMechanics(); + + bool adaptProblem(ESpellCastProblem source, Problem & target) const override; + bool adaptGenericProblem(Problem & target) const override; + + int32_t getSpellIndex() const override; + SpellID getSpellId() const override; + std::string getSpellName() const override; + int32_t getSpellLevel() const override; + + IBattleCast::Value getEffectLevel() const override; + IBattleCast::Value getRangeLevel() const override; + + IBattleCast::Value getEffectPower() const override; + IBattleCast::Value getEffectDuration() const override; + + IBattleCast::Value64 getEffectValue() const override; + + PlayerColor getCasterColor() const override; + + bool isSmart() const override; + bool isMassive() const override; + bool requiresClearTiles() const override; + bool alwaysHitFirstTarget() const override; + + bool isNegativeSpell() const override; + bool isPositiveSpell() const override; + bool isMagicalEffect() const override; + + int64_t adjustEffectValue(const battle::Unit * target) const override; + int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override; + int64_t applySpecificSpellBonus(int64_t value) const override; + int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override; + + bool ownerMatches(const battle::Unit * unit) const override; + bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override; + + std::vector getTargetTypes() const override; + + const CreatureService * creatures() const override; +#if SCRIPTING_ENABLED + const scripting::Service * scripts() const override; +#endif + const Service * spells() const override; + + const CBattleInfoCallback * battle() const override; + +protected: + const CSpell * owner; + Mode mode; + + BaseMechanics(const IBattleCast * event); + +private: + IBattleCast::Value rangeLevel; + IBattleCast::Value effectLevel; + + ///actual spell-power affecting effect values + IBattleCast::Value effectPower; + ///actual spell-power affecting effect duration + IBattleCast::Value effectDuration; + + ///raw damage/heal amount + IBattleCast::Value64 effectValue; + + boost::logic::tribool smart; + boost::logic::tribool massive; + + const CBattleInfoCallback * cb; +}; + +class DLL_LINKAGE IReceptiveCheck +{ +public: + virtual ~IReceptiveCheck() = default; + + virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0; +}; + +}// namespace spells + +class DLL_LINKAGE AdventureSpellCastParameters +{ +public: + const spells::Caster * caster; + int3 pos; +}; + +class DLL_LINKAGE IAdventureSpellMechanics +{ +public: + IAdventureSpellMechanics(const CSpell * s); + virtual ~IAdventureSpellMechanics() = default; + + virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0; + virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0; + + virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0; + + static std::unique_ptr createMechanics(const CSpell * s); +protected: + const CSpell * owner; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Effect.h b/lib/spells/effects/Effect.h index f20af46c3..8cb5f446c 100644 --- a/lib/spells/effects/Effect.h +++ b/lib/spells/effects/Effect.h @@ -1,83 +1,83 @@ -/* - * Effect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleHex; -class BattleHexArray; -class CBattleInfoCallback; -class JsonSerializeFormat; -class ServerCallback; - -namespace vstd -{ - class RNG; -} - -namespace spells -{ -using EffectTarget = Target; - -namespace effects -{ -using RNG = vstd::RNG; -class Effects; -class Effect; -class Registry; - -using TargetType = spells::AimType; - -class DLL_LINKAGE Effect -{ -public: - bool indirect = false; - bool optional = false; - - std::string name; - - virtual ~Effect() = default; //Required for child classes - - // TODO: document me - virtual void adjustTargetTypes(std::vector & types) const = 0; - - /// Generates list of hexes affected by spell, if spell were to cast at specified target - virtual void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const = 0; - - /// Returns whether effect has any valid targets on the battlefield - virtual bool applicable(Problem & problem, const Mechanics * m) const; - - /// Returns whether effect is valid and can be applied onto selected target - virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const; - - virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0; - - /// Processes input target and generates subset-result that contains only valid targets - virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0; - - // TODO: document me - virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0; - - /// Serializes (or deserializes) parameters of Effect - void serializeJson(JsonSerializeFormat & handler); - - static std::shared_ptr create(const Registry * registry, const std::string & type); - -protected: - virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Effect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleHex; +class BattleHexArray; +class CBattleInfoCallback; +class JsonSerializeFormat; +class ServerCallback; + +namespace vstd +{ + class RNG; +} + +namespace spells +{ +using EffectTarget = Target; + +namespace effects +{ +using RNG = vstd::RNG; +class Effects; +class Effect; +class Registry; + +using TargetType = spells::AimType; + +class DLL_LINKAGE Effect +{ +public: + bool indirect = false; + bool optional = false; + + std::string name; + + virtual ~Effect() = default; //Required for child classes + + // TODO: document me + virtual void adjustTargetTypes(std::vector & types) const = 0; + + /// Generates list of hexes affected by spell, if spell were to cast at specified target + virtual void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const = 0; + + /// Returns whether effect has any valid targets on the battlefield + virtual bool applicable(Problem & problem, const Mechanics * m) const; + + /// Returns whether effect is valid and can be applied onto selected target + virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const; + + virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0; + + /// Processes input target and generates subset-result that contains only valid targets + virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0; + + // TODO: document me + virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0; + + /// Serializes (or deserializes) parameters of Effect + void serializeJson(JsonSerializeFormat & handler); + + static std::shared_ptr create(const Registry * registry, const std::string & type); + +protected: + virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/LocationEffect.cpp b/lib/spells/effects/LocationEffect.cpp index 82ed0f54c..a1b1c3419 100644 --- a/lib/spells/effects/LocationEffect.cpp +++ b/lib/spells/effects/LocationEffect.cpp @@ -1,52 +1,52 @@ -/* - * LocationEffect.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include "LocationEffect.h" -#include "../ISpellMechanics.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -void LocationEffect::adjustTargetTypes(std::vector & types) const -{ - -} - -void LocationEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const -{ - for(const auto & destnation : spellTarget) - hexes.insert(destnation.hexValue); -} - -EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const -{ - EffectTarget res; - vstd::copy_if(target, std::back_inserter(res), [](const Destination & d) - { - return !d.unitValue && (d.hexValue.isValid()); - }); - return res; -} - -EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const -{ - //by default effect covers exactly spell range - return EffectTarget(spellTarget); -} - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * LocationEffect.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "LocationEffect.h" +#include "../ISpellMechanics.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +void LocationEffect::adjustTargetTypes(std::vector & types) const +{ + +} + +void LocationEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const +{ + for(const auto & destnation : spellTarget) + hexes.insert(destnation.hexValue); +} + +EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const +{ + EffectTarget res; + vstd::copy_if(target, std::back_inserter(res), [](const Destination & d) + { + return !d.unitValue && (d.hexValue.isValid()); + }); + return res; +} + +EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const +{ + //by default effect covers exactly spell range + return EffectTarget(spellTarget); +} + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/LocationEffect.h b/lib/spells/effects/LocationEffect.h index a47c7e608..8a99d163b 100644 --- a/lib/spells/effects/LocationEffect.h +++ b/lib/spells/effects/LocationEffect.h @@ -1,41 +1,41 @@ -/* - * LocationEffect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" - -VCMI_LIB_NAMESPACE_BEGIN - - -namespace spells -{ -namespace effects -{ - -class LocationEffect : public Effect -{ -public: - void adjustTargetTypes(std::vector & types) const override; - - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; -protected: - -private: -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * LocationEffect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" + +VCMI_LIB_NAMESPACE_BEGIN + + +namespace spells +{ +namespace effects +{ + +class LocationEffect : public Effect +{ +public: + void adjustTargetTypes(std::vector & types) const override; + + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; +protected: + +private: +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Moat.h b/lib/spells/effects/Moat.h index 4e0c020f8..ac940fc7d 100644 --- a/lib/spells/effects/Moat.h +++ b/lib/spells/effects/Moat.h @@ -1,43 +1,43 @@ -/* - * Moat.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Obstacle.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct Bonus; - -namespace spells -{ -namespace effects -{ - -class Moat : public Obstacle -{ -private: - ObstacleSideOptions sideOptions; //Defender only - std::vector moatHexes; //Determine number of moat patches and hexes - std::vector> bonus; //For battle-wide bonuses - bool dispellable; //For Tower landmines - int moatDamage; // Minimal moat damage -public: - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override; - void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - void convertBonus(const Mechanics * m, std::vector & converted) const; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Moat.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Obstacle.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct Bonus; + +namespace spells +{ +namespace effects +{ + +class Moat : public Obstacle +{ +private: + ObstacleSideOptions sideOptions; //Defender only + std::vector moatHexes; //Determine number of moat patches and hexes + std::vector> bonus; //For battle-wide bonuses + bool dispellable; //For Tower landmines + int moatDamage; // Minimal moat damage +public: + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override; + void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + void convertBonus(const Mechanics * m, std::vector & converted) const; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Obstacle.h b/lib/spells/effects/Obstacle.h index 7677d4523..2c3624669 100644 --- a/lib/spells/effects/Obstacle.h +++ b/lib/spells/effects/Obstacle.h @@ -1,78 +1,78 @@ -/* - * Obstacle.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "LocationEffect.h" -#include "../../GameConstants.h" -#include "../../battle/BattleHexArray.h" -#include "../../battle/CObstacleInstance.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class ObstacleSideOptions -{ -public: - using RelativeShape = std::vector>; - - RelativeShape shape; //shape of single obstacle relative to obstacle position - RelativeShape range; //position of obstacles relative to effect destination - - AudioPath appearSound; - AnimationPath appearAnimation; - AnimationPath animation; - - int offsetY = 0; - - void serializeJson(JsonSerializeFormat & handler); -}; - -class Obstacle : public LocationEffect -{ -public: - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override; - virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const; - - bool hidden = false; - bool trigger = false; - bool trap = false; - bool removeOnTrigger = false; - bool hideNative = false; - SpellID triggerAbility; -private: - int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine) - bool passable = false; - int32_t turnsRemaining = -1; - - BattleSideArray sideOptions; - - static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear); - static bool noRoomToPlace(Problem & problem, const Mechanics * m); -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Obstacle.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "LocationEffect.h" +#include "../../GameConstants.h" +#include "../../battle/BattleHexArray.h" +#include "../../battle/CObstacleInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class ObstacleSideOptions +{ +public: + using RelativeShape = std::vector>; + + RelativeShape shape; //shape of single obstacle relative to obstacle position + RelativeShape range; //position of obstacles relative to effect destination + + AudioPath appearSound; + AnimationPath appearAnimation; + AnimationPath animation; + + int offsetY = 0; + + void serializeJson(JsonSerializeFormat & handler); +}; + +class Obstacle : public LocationEffect +{ +public: + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override; + virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const; + + bool hidden = false; + bool trigger = false; + bool trap = false; + bool removeOnTrigger = false; + bool hideNative = false; + SpellID triggerAbility; +private: + int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine) + bool passable = false; + int32_t turnsRemaining = -1; + + BattleSideArray sideOptions; + + static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear); + static bool noRoomToPlace(Problem & problem, const Mechanics * m); +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Summon.h b/lib/spells/effects/Summon.h index 55b09fc47..dce5e1c4a 100644 --- a/lib/spells/effects/Summon.h +++ b/lib/spells/effects/Summon.h @@ -1,55 +1,55 @@ -/* - * Summon.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" -#include "../../GameConstants.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class Summon : public Effect -{ -public: - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - void adjustTargetTypes(std::vector & types) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override final; - -private: - int32_t summonedCreatureAmount(const Mechanics * m) const; - int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const; - - CreatureID creature; - - bool permanent = false; - bool exclusive = true; - bool summonByHealth = false; - bool summonSameUnit = false; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Summon.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" +#include "../../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class Summon : public Effect +{ +public: + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + void adjustTargetTypes(std::vector & types) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override final; + +private: + int32_t summonedCreatureAmount(const Mechanics * m) const; + int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const; + + CreatureID creature; + + bool permanent = false; + bool exclusive = true; + bool summonByHealth = false; + bool summonSameUnit = false; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/UnitEffect.h b/lib/spells/effects/UnitEffect.h index 521e5ab0a..de2b481eb 100644 --- a/lib/spells/effects/UnitEffect.h +++ b/lib/spells/effects/UnitEffect.h @@ -1,61 +1,61 @@ -/* - * UnitEffect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class UnitEffect : public Effect -{ -public: - void adjustTargetTypes(std::vector & types) const override; - - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - - bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const; - - virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const; - -protected: - int32_t chainLength = 0; - double chainFactor = 0.0; - - virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const; - virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const; - virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const; - - void serializeJsonEffect(JsonSerializeFormat & handler) override final; - virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0; - -private: - bool ignoreImmunity = false; - - EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; - EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * UnitEffect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class UnitEffect : public Effect +{ +public: + void adjustTargetTypes(std::vector & types) const override; + + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + + bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const; + + virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const; + +protected: + int32_t chainLength = 0; + double chainFactor = 0.0; + + virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const; + virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const; + virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const; + + void serializeJsonEffect(JsonSerializeFormat & handler) override final; + virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0; + +private: + bool ignoreImmunity = false; + + EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; + EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/server/battles/BattleFlowProcessor.h b/server/battles/BattleFlowProcessor.h index 6982f739d..b628bbc6a 100644 --- a/server/battles/BattleFlowProcessor.h +++ b/server/battles/BattleFlowProcessor.h @@ -1,62 +1,62 @@ -/* - * BattleFlowProcessor.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../lib/battle/BattleSide.h" - -VCMI_LIB_NAMESPACE_BEGIN -class CStack; -struct BattleHex; -class BattleHexArray; -class BattleAction; -class CBattleInfoCallback; -struct CObstacleInstance; -namespace battle -{ -class Unit; -} -VCMI_LIB_NAMESPACE_END - -class CGameHandler; -class BattleProcessor; - -/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions -class BattleFlowProcessor : boost::noncopyable -{ - BattleProcessor * owner; - CGameHandler * gameHandler; - - const CStack * getNextStack(const CBattleInfoCallback & battle); - - bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack); - bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack); - - void summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex); - void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack); - void tryPlaceMoats(const CBattleInfoCallback & battle); - void castOpeningSpells(const CBattleInfoCallback & battle); - void activateNextStack(const CBattleInfoCallback & battle); - void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound); - - void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack); - void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle); - void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack); - void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack); - - void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next); - bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) - -public: - explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler); - - void onBattleStarted(const CBattleInfoCallback & battle); - void onTacticsEnded(const CBattleInfoCallback & battle); - void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba); -}; +/* + * BattleFlowProcessor.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../lib/battle/BattleSide.h" + +VCMI_LIB_NAMESPACE_BEGIN +class CStack; +struct BattleHex; +class BattleHexArray; +class BattleAction; +class CBattleInfoCallback; +struct CObstacleInstance; +namespace battle +{ +class Unit; +} +VCMI_LIB_NAMESPACE_END + +class CGameHandler; +class BattleProcessor; + +/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions +class BattleFlowProcessor : boost::noncopyable +{ + BattleProcessor * owner; + CGameHandler * gameHandler; + + const CStack * getNextStack(const CBattleInfoCallback & battle); + + bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack); + bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack); + + void summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex); + void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack); + void tryPlaceMoats(const CBattleInfoCallback & battle); + void castOpeningSpells(const CBattleInfoCallback & battle); + void activateNextStack(const CBattleInfoCallback & battle); + void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound); + + void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack); + void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle); + void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack); + void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack); + + void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next); + bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) + +public: + explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler); + + void onBattleStarted(const CBattleInfoCallback & battle); + void onTacticsEnded(const CBattleInfoCallback & battle); + void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba); +}; From e3516120d8ad27a99616ebcce9362c748e839c4e Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Mon, 2 Dec 2024 13:06:58 +0100 Subject: [PATCH 06/14] Refactor destructibleEnemyTurns --- lib/battle/AccessibilityInfo.cpp | 5 +++-- lib/battle/AccessibilityInfo.h | 4 ++-- lib/battle/CBattleInfoCallback.cpp | 31 ++++++++++++++---------------- lib/battle/ReachabilityInfo.cpp | 1 + lib/battle/ReachabilityInfo.h | 7 +++++-- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/battle/AccessibilityInfo.cpp b/lib/battle/AccessibilityInfo.cpp index 22fa5196e..e2f1eab15 100644 --- a/lib/battle/AccessibilityInfo.cpp +++ b/lib/battle/AccessibilityInfo.cpp @@ -22,9 +22,10 @@ bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) if(accessibility == EAccessibility::ALIVE_STACK) { - auto destructible = destructibleEnemyTurns.find(tile); + if(!destructibleEnemyTurns) + return false; - return destructible != destructibleEnemyTurns.end(); + return destructibleEnemyTurns->at(tile.hex) >= 0; } if(accessibility != EAccessibility::ACCESSIBLE) diff --git a/lib/battle/AccessibilityInfo.h b/lib/battle/AccessibilityInfo.h index 1352c4da2..a9fd9722d 100644 --- a/lib/battle/AccessibilityInfo.h +++ b/lib/battle/AccessibilityInfo.h @@ -27,7 +27,7 @@ enum class EAccessibility DESTRUCTIBLE_WALL, GATE, //sieges -> gate opens only for defender stacks UNAVAILABLE, //indestructible wall parts, special battlefields (like boat-to-boat) - SIDE_COLUMN //used for first and last columns of hexes that are unavailable but wat machines can stand there + SIDE_COLUMN //used for first and last columns of hexes that are unavailable but war machines can stand there }; @@ -35,7 +35,7 @@ using TAccessibilityArray = std::array destructibleEnemyTurns; + const std::array * destructibleEnemyTurns = nullptr; public: bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index ef567ccf4..d3e259c8f 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -1083,28 +1083,25 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[curHex.hex]) { - if(neighbour.isValid()) + auto additionalCost = 0; + + if(params.bypassEnemyStacks) { - auto additionalCost = 0; + auto enemyToBypass = params.destructibleEnemyTurns.at(neighbour); - if(params.bypassEnemyStacks) + if(enemyToBypass >= 0) { - auto enemyToBypass = params.destructibleEnemyTurns.find(neighbour); - - if(enemyToBypass != params.destructibleEnemyTurns.end()) - { - additionalCost = enemyToBypass->second; - } + additionalCost = enemyToBypass; } + } - const int costFoundSoFar = ret.distances[neighbour.hex]; + const int costFoundSoFar = ret.distances[neighbour.hex]; - if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar) - { - hexq.push(neighbour); - ret.distances[neighbour.hex] = costToNeighbour + additionalCost; - ret.predecessors[neighbour.hex] = curHex; - } + if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar) + { + hexq.push(neighbour); + ret.distances[neighbour.hex] = costToNeighbour + additionalCost; + ret.predecessors[neighbour.hex] = curHex; } } } @@ -1280,7 +1277,7 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa { auto accessibility = getAccessibility(params.knownAccessible); - accessibility.destructibleEnemyTurns = params.destructibleEnemyTurns; + accessibility.destructibleEnemyTurns = & params.destructibleEnemyTurns; return makeBFS(accessibility, params); } diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index e00393d4a..c94d8b830 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -22,6 +22,7 @@ ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex S flying(Stack->hasBonusOfType(BonusType::FLYING)) { knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); + destructibleEnemyTurns.fill(-1); } ReachabilityInfo::ReachabilityInfo() diff --git a/lib/battle/ReachabilityInfo.h b/lib/battle/ReachabilityInfo.h index ddd702d9f..af09b17e3 100644 --- a/lib/battle/ReachabilityInfo.h +++ b/lib/battle/ReachabilityInfo.h @@ -33,12 +33,15 @@ struct DLL_LINKAGE ReachabilityInfo bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward BattleHexArray knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself) - std::map destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex + std::array destructibleEnemyTurns; // how many turns it is needed to kill enemy on specific hex (index <=> hex) BattleHex startPosition; //assumed position of stack BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side - Parameters() = default; + Parameters() + { + destructibleEnemyTurns.fill(-1); + } Parameters(const battle::Unit * Stack, BattleHex StartPosition); }; From fb9a3da651ec4b381658a59c79bf33371d4b6b7d Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Wed, 4 Dec 2024 21:37:31 +0100 Subject: [PATCH 07/14] Unit.cpp refactor and some other minor changes --- AI/BattleAI/AttackPossibility.cpp | 2 +- AI/BattleAI/BattleEvaluator.cpp | 24 ++--- AI/BattleAI/BattleExchangeVariant.cpp | 32 +++---- AI/BattleAI/BattleExchangeVariant.h | 2 +- lib/battle/BattleHexArray.cpp | 133 ++++++++++++-------------- lib/battle/BattleHexArray.h | 15 ++- lib/battle/CBattleInfoCallback.cpp | 36 ++++--- lib/battle/CBattleInfoCallback.h | 2 +- lib/battle/ReachabilityInfo.cpp | 2 +- lib/battle/ReachabilityInfo.h | 2 +- lib/battle/Unit.cpp | 61 +++++------- lib/battle/Unit.h | 10 +- 12 files changed, 142 insertions(+), 179 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index 874ea8321..1c25b0512 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -280,7 +280,7 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg( std::set checkedUnits; auto attacker = attackInfo.attacker; - auto hexes = attacker->getSurroundingHexes(hex); + const BattleHexArray & hexes = attacker->getSurroundingHexes(hex); for(BattleHex tile : hexes) { auto st = state->battleGetUnitByPos(tile, true); diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 6beb15dc4..408d4d96e 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -278,7 +278,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) score = moveTarget.score; cachedAttack.ap = moveTarget.cachedAttack; cachedAttack.score = score; - cachedAttack.turn = moveTarget.turnsToRich; + cachedAttack.turn = moveTarget.turnsToReach; if(stack->waited()) { @@ -390,11 +390,11 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr return reachability.distances[h1] < reachability.distances[h2]; }); - BattleHex bestNeighbor = targetHexes.front(); + BattleHex bestNeighbour = hexes.front(); - if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE) + if(reachability.distances[bestNeighbour] > GameConstants::BFIELD_SIZE) { - logAi->trace("No richable hexes."); + logAi->trace("No reachable hexes."); return BattleAction::makeDefend(stack); } @@ -421,22 +421,17 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr { BattleHexArray obstacleHexes; - auto insertAffected = [](const CObstacleInstance & spellObst, BattleHexArray & obstacleHexes) { - auto affectedHexes = spellObst.getAffectedTiles(); - obstacleHexes.merge(affectedHexes); - }; - const auto & obstacles = hb->battleGetAllObstacles(); - for (const auto & obst : obstacles) { - + for (const auto & obst : obstacles) + { if(obst->triggersEffects()) { auto triggerAbility = VLC->spells()->getById(obst->getTrigger()); auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage(); if(triggerIsNegative) - insertAffected(*obst, obstacleHexes); + obstacleHexes.merge(obst->getAffectedTiles()); } } // Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. @@ -446,7 +441,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr const int NEGATIVE_OBSTACLE_PENALTY = 100; // avoid landing on negative obstacle (moat, fire wall, etc) const int BLOCKED_STACK_PENALTY = 100; // avoid landing on moat - auto distance = BattleHex::getDistance(bestNeighbor, hex); + auto distance = BattleHex::getDistance(bestNeighbour, hex); if(vstd::contains(obstacleHexes, hex)) distance += NEGATIVE_OBSTACLE_PENALTY; @@ -458,7 +453,8 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr } else { - BattleHex currentDest = bestNeighbor; + BattleHex currentDest = bestNeighbour; + while(true) { if(!currentDest.isValid()) diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 69bbb1a5f..66fe316f4 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -21,7 +21,7 @@ AttackerValue::AttackerValue() MoveTarget::MoveTarget() : positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE) { - turnsToRich = 1; + turnsToReach = 1; } float BattleExchangeVariant::trackAttack( @@ -361,7 +361,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( float penaltyMultiplier = 1.0f; // Default multiplier, no penalty float closestAllyDistance = std::numeric_limits::max(); - for (const battle::Unit* ally : hb->battleAliveUnits()) { + for (const battle::Unit* ally : hb->battleAliveUnits()) + { if (ally == activeStack) continue; if (ally->unitSide() != activeStack->unitSide()) @@ -375,12 +376,13 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( } // If an ally is closer to the enemy, compute the penaltyMultiplier - if (closestAllyDistance < distance) { + if (closestAllyDistance < distance) + { penaltyMultiplier = closestAllyDistance / distance; // Ratio of distances } - auto turnsToRich = (distance - 1) / speed + 1; - auto hexes = enemy->getSurroundingHexes(); + auto turnsToReach = (distance - 1) / speed + 1; + const BattleHexArray & hexes = enemy->getSurroundingHexes(); auto enemySpeed = enemy->getMovementRange(); auto speedRatio = speed / static_cast(enemySpeed); auto multiplier = (speedRatio > 1 ? 1 : speedRatio) * penaltyMultiplier; @@ -393,16 +395,16 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure - auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb); + auto score = calculateExchange(attack, turnsToReach, targets, damageCache, hb); score.enemyDamageReduce *= multiplier; #if BATTLE_TRACE_LEVEL >= 1 - logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score)); + logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToReach, result.score, scoreValue(score)); #endif if(result.score < scoreValue(score) - || (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score)))) + || (result.turnsToReach > turnsToReach && vstd::isAlmostEqual(result.score, scoreValue(score)))) { result.score = scoreValue(score); result.positions.clear(); @@ -411,10 +413,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( logAi->trace("New high score"); #endif - for(const BattleHex & initialEnemyHex : enemy->getAttackableHexes(activeStack)) + for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack)) { - BattleHex enemyHex = initialEnemyHex; - while(!flying && dists.distances[enemyHex] > speed && dists.predecessors.at(enemyHex).isValid()) { enemyHex = dists.predecessors.at(enemyHex); @@ -462,7 +462,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( } result.cachedAttack = attack; - result.turnsToRich = turnsToRich; + result.turnsToReach = turnsToReach; } } } @@ -484,7 +484,7 @@ std::vector BattleExchangeEvaluator::getAdjacentUnits(cons queue.pop(); checkedStacks.push_back(stack); - auto hexes = stack->getSurroundingHexes(); + auto const & hexes = stack->getSurroundingHexes(); for(auto hex : hexes) { auto neighbour = cb->battleGetUnitByPos(hex); @@ -511,7 +511,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits( auto hexes = ap.attack.defender->getSurroundingHexes(); - if(!ap.attack.shooting) hexes.insert(ap.from); + if(!ap.attack.shooting) + hexes.insert(ap.from); std::vector allReachableUnits = additionalUnits; @@ -959,8 +960,7 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) { - const BattleHexArray & neighbours = BattleHexArray::neighbouringTilesCache[hex.hex]; - for(BattleHex neighbour : neighbours) + for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[hex.hex]) { reachable = unitReachability.distances.at(neighbour) <= radius; diff --git a/AI/BattleAI/BattleExchangeVariant.h b/AI/BattleAI/BattleExchangeVariant.h index 844890778..0aba34749 100644 --- a/AI/BattleAI/BattleExchangeVariant.h +++ b/AI/BattleAI/BattleExchangeVariant.h @@ -56,7 +56,7 @@ struct MoveTarget float score; BattleHexArray positions; std::optional cachedAttack; - uint8_t turnsToRich; + uint8_t turnsToReach; MoveTarget(); }; diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index e36e98e96..f7bd751e8 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -55,42 +55,11 @@ BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal); return (bestTile != closestTiles.end()) ? *bestTile : BattleHex(); - - //BattleHex initialHex = BattleHex(initialPos); - //auto compareDistance = [initialHex](const BattleHex left, const BattleHex right) -> bool - //{ - // return initialHex.getDistance(initialHex, left) < initialHex.getDistance(initialHex, right); - //}; - //BattleHexArray sortedTiles(*this); - //boost::sort(sortedTiles, compareDistance); //closest tiles at front - //int closestDistance = initialHex.getDistance(initialPos, sortedTiles.front()); //sometimes closest tiles can be many hexes away - //auto notClosest = [closestDistance, initialPos](const BattleHex here) -> bool - //{ - // return closestDistance < here.getDistance(initialPos, here); - //}; - //vstd::erase_if(sortedTiles, notClosest); //only closest tiles are interesting - //auto compareHorizontal = [side, initialPos](const BattleHex left, const BattleHex right) -> bool - //{ - // if(left.getX() != right.getX()) - // { - // if(side == BattleSide::ATTACKER) - // return left.getX() > right.getX(); //find furthest right - // else - // return left.getX() < right.getX(); //find furthest left - // } - // else - // { - // //Prefer tiles in the same row. - // return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); - // } - //}; - //boost::sort(sortedTiles, compareHorizontal); - //return sortedTiles.front(); } -BattleHexArray::NeighbouringTilesCache BattleHexArray::calculateNeighbouringTiles() +BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::calculateNeighbouringTiles() { - BattleHexArray::NeighbouringTilesCache ret; + BattleHexArray::ArrayOfBattleHexArrays ret; for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) { @@ -110,50 +79,75 @@ BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) BattleHexArray ret; for(auto dir : BattleHex::hexagonalDirections()) ret.checkAndPush(hex.cloneInDirection(dir, false)); - + return ret; } -BattleHexArray BattleHexArray::generateAttackerClosestTilesCache() +const BattleHexArray & BattleHexArray::getNeighbouringTilesDblWide(BattleHex pos, BattleSide side) +{ + static std::array ret; // 2 -> only two valid sides: ATTACKER and DEFENDER + size_t sideIdx = static_cast(side); + static bool initialized[2] = { false, false }; + + if(!initialized[sideIdx]) + { + // first run, need to initialize + + for(BattleHex hex = 0; hex < GameConstants::BFIELD_SIZE; hex.hex++) + { + BattleHexArray hexes; + + if(side == BattleSide::ATTACKER) + { + const BattleHex otherHex = hex - 1; + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(hex.cloneInDirection(dir, false)); + + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + } + else if(side == BattleSide::DEFENDER) + { + const BattleHex otherHex = hex + 1; + + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); + + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false)); + } + ret[sideIdx][hex.hex] = std::move(hexes); + } + initialized[sideIdx] = true; + } + + return ret[sideIdx][pos.hex]; +} + +const BattleHexArray & BattleHexArray::getClosestTilesCache(BattleHex pos, BattleSide side) { assert(!neighbouringTilesCache.empty()); - BattleHexArray ret; + static std::array ret; + static bool initialized = false; + size_t sideIdx = static_cast(side); - ret.resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + if(!initialized) { - ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); + ret[sideIdx].resize(GameConstants::BFIELD_SIZE); + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret[sideIdx].set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); + } + initialized = true; } - return ret; -} - -BattleHexArray BattleHexArray::generateDefenderClosestTilesCache() -{ - assert(!neighbouringTilesCache.empty()); - - BattleHexArray ret; - - ret.resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - ret.set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::DEFENDER, hex)); - } - - return ret; -} - -BattleHex BattleHexArray::getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos) -{ - if(side == BattleSide::ATTACKER) - return closestTilesCacheForAttacker[pos.hex]; - else if(side == BattleSide::DEFENDER) - return closestTilesCacheForDefender[pos.hex]; - else - assert(false); // we should never be here + return ret[sideIdx]; } void BattleHexArray::merge(const BattleHexArray & other) noexcept @@ -182,9 +176,6 @@ void BattleHexArray::clear() noexcept internalStorage.clear(); } -const BattleHexArray::NeighbouringTilesCache BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles(); - -const BattleHexArray BattleHexArray::closestTilesCacheForAttacker = generateAttackerClosestTilesCache(); -const BattleHexArray BattleHexArray::closestTilesCacheForDefender = generateDefenderClosestTilesCache(); +const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles(); VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 4c9810328..5c51198ae 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -33,11 +33,9 @@ public: using reverse_iterator = typename StorageType::reverse_iterator; using const_reverse_iterator = typename StorageType::const_reverse_iterator; - using NeighbouringTilesCache = std::array; + using ArrayOfBattleHexArrays = std::array; - static const NeighbouringTilesCache neighbouringTilesCache; - static const BattleHexArray closestTilesCacheForAttacker; - static const BattleHexArray closestTilesCacheForDefender; + static const ArrayOfBattleHexArrays neighbouringTilesCache; BattleHexArray() noexcept { @@ -130,7 +128,8 @@ public: return internalStorage.insert(pos, hex); } - static BattleHex getClosestTileFromAllPossibleNeighbours(BattleSide side, BattleHex pos); + static const BattleHexArray & getClosestTilesCache(BattleHex pos, BattleSide side); + static const BattleHexArray & getNeighbouringTilesDblWide(BattleHex pos, BattleSide side); BattleHex getClosestTile(BattleSide side, BattleHex initialPos) const; @@ -190,7 +189,7 @@ public: logGlobal->warn("BattleHexArray::contains( %d ) - invalid BattleHex!", hex); */ - // return true for invalid hexes + // returns true also for invalid hexes return true; } @@ -298,10 +297,8 @@ private: } /// returns all valid neighbouring tiles - static BattleHexArray::NeighbouringTilesCache calculateNeighbouringTiles(); + static BattleHexArray::ArrayOfBattleHexArrays calculateNeighbouringTiles(); static BattleHexArray generateNeighbouringTiles(BattleHex hex); - static BattleHexArray generateAttackerClosestTilesCache(); - static BattleHexArray generateDefenderClosestTilesCache(); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index d3e259c8f..c62cfba0e 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -651,7 +651,7 @@ BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * if(!otherSt->isValidTarget(false)) continue; - BattleHexArray occupied = otherSt->getHexes(); + const BattleHexArray & occupied = otherSt->getHexes(); if(battleCanShoot(unit, otherSt->getPosition())) { @@ -964,9 +964,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const if(bFieldType != BattleField::NONE) { - BattleHexArray impassableHexes = bFieldType.getInfo()->impassableHexes; - - for(auto hex : impassableHexes) + for(auto hex : bFieldType.getInfo()->impassableHexes) ret[hex] = EAccessibility::UNAVAILABLE; } @@ -1031,20 +1029,20 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const AccessibilityInfo CBattleInfoCallback::getAccessibility(const battle::Unit * stack) const { - return getAccessibility(battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide())); + return getAccessibility(& battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide())); } -AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray & accessibleHexes) const +AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray * accessibleHexes) const { auto ret = getAccessibility(); - for(auto hex : accessibleHexes) + for(auto hex : *accessibleHexes) if(hex.isValid()) ret[hex] = EAccessibility::ACCESSIBLE; return ret; } -ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibility, const ReachabilityInfo::Parameters & params) const +ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessibility, const ReachabilityInfo::Parameters & params) const { ReachabilityInfo ret; ret.accessibility = accessibility; @@ -1114,11 +1112,11 @@ bool CBattleInfoCallback::isInObstacle( const BattleHexArray & obstacleHexes, const ReachabilityInfo::Parameters & params) const { - auto occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side); + const BattleHexArray & occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side); for(auto occupiedHex : occupiedHexes) { - if(params.ignoreKnownAccessible && params.knownAccessible.contains(occupiedHex)) + if(params.ignoreKnownAccessible && params.knownAccessible->contains(occupiedHex)) continue; if(obstacleHexes.contains(occupiedHex)) @@ -1146,7 +1144,7 @@ BattleHexArray CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) if(!battleIsObstacleVisibleForSide(*oi, whichSidePerspective)) continue; - for(const auto & hex : oi->getStoppingTile()) + for(auto hex : oi->getStoppingTile()) { if(hex == BattleHex::GATE_BRIDGE && oi->obstacleType == CObstacleInstance::MOAT) { @@ -1162,7 +1160,6 @@ BattleHexArray CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) std::pair CBattleInfoCallback::getNearestStack(const battle::Unit * closest) const { auto reachability = getReachability(closest); - auto avHexes = battleGetAvailableHexes(reachability, closest, false); // I hate std::pairs with their undescriptive member names first / second struct DistStack @@ -1181,7 +1178,7 @@ std::pair CBattleInfoCallback::getNearestStack( for(const battle::Unit * st : possible) { - for(BattleHex hex : avHexes) + for(BattleHex hex : battleGetAvailableHexes(reachability, closest, false)) if(CStack::isMeleeAttackPossible(closest, st, hex)) { DistStack hlp = {reachability.distances[hex], hex, st}; @@ -1269,7 +1266,7 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const battle::Unit * unit) return getReachability(params); } -ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters ¶ms) const +ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Parameters & params) const { if(params.flying) return getFlyingReachability(params); @@ -1283,7 +1280,7 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa } } -ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters ¶ms) const +ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters & params) const { ReachabilityInfo ret; ret.accessibility = getAccessibility(params.knownAccessible); @@ -1340,11 +1337,11 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( } if(attacker->hasBonusOfType(BonusType::ATTACKS_ALL_ADJACENT)) { - boost::copy(attacker->getSurroundingHexes(attackerPos), vstd::set_inserter(at.hostileCreaturePositions)); + at.hostileCreaturePositions.merge(attacker->getSurroundingHexes(attackerPos)); } if(attacker->hasBonusOfType(BonusType::THREE_HEADED_ATTACK)) { - BattleHexArray hexes = attacker->getSurroundingHexes(attackerPos); + const BattleHexArray & hexes = attacker->getSurroundingHexes(attackerPos); for(BattleHex tile : hexes) { if((BattleHex::mutualPosition(tile, destinationTile) > -1 && BattleHex::mutualPosition(tile, attackOriginHex) > -1)) //adjacent both to attacker's head and attacked tile @@ -1432,9 +1429,8 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle:: if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::neighbouringTilesCache[attackerPos].contains(destinationTile)) { - auto targetHexes = BattleHexArray::neighbouringTilesCache[destinationTile]; - targetHexes.insert(destinationTile); - boost::copy(targetHexes, vstd::set_inserter(at.hostileCreaturePositions)); + at.hostileCreaturePositions.merge(BattleHexArray::neighbouringTilesCache[destinationTile]); + at.hostileCreaturePositions.insert(destinationTile); } return at; diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index e7c11d5da..3d877a5e1 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -162,7 +162,7 @@ public: ReachabilityInfo getReachability(const ReachabilityInfo::Parameters & params) const; AccessibilityInfo getAccessibility() const; AccessibilityInfo getAccessibility(const battle::Unit * stack) const; //Hexes occupied by stack will be marked as accessible. - AccessibilityInfo getAccessibility(const BattleHexArray & accessibleHexes) const; //given hexes will be marked as accessible + AccessibilityInfo getAccessibility(const BattleHexArray * accessibleHexes) const; //given hexes will be marked as accessible std::pair getNearestStack(const battle::Unit * closest) const; BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index c94d8b830..069c9a5cf 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -21,7 +21,7 @@ ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex S side(Stack->unitSide()), flying(Stack->hasBonusOfType(BonusType::FLYING)) { - knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); + knownAccessible = & battle::Unit::getHexes(startPosition, doubleWide, side); destructibleEnemyTurns.fill(-1); } diff --git a/lib/battle/ReachabilityInfo.h b/lib/battle/ReachabilityInfo.h index af09b17e3..29687df66 100644 --- a/lib/battle/ReachabilityInfo.h +++ b/lib/battle/ReachabilityInfo.h @@ -32,7 +32,7 @@ struct DLL_LINKAGE ReachabilityInfo bool flying = false; bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward - BattleHexArray knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself) + const BattleHexArray * knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself) std::array destructibleEnemyTurns; // how many turns it is needed to kill enemy on specific hex (index <=> hex) BattleHex startPosition; //assumed position of stack diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index 021173901..18a634eef 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -51,53 +51,26 @@ const IBonusBearer* Unit::getBonusBearer() const return this; } -BattleHexArray Unit::getSurroundingHexes(BattleHex assumedPosition) const +const BattleHexArray & Unit::getSurroundingHexes(BattleHex assumedPosition) const { BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position return getSurroundingHexes(hex, doubleWide(), unitSide()); } -BattleHexArray Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) +const BattleHexArray & Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) { - if(!position.isValid()) - return { }; - - BattleHexArray hexes; - if(twoHex) - { - const BattleHex otherHex = occupiedHex(position, twoHex, side); - - if(side == BattleSide::ATTACKER) - { - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(position.cloneInDirection(dir, false)); - - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - } - else - { - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); - - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(position.cloneInDirection(BattleHex::EDir::LEFT, false)); - } - return hexes; - } - else - { + assert(position.isValid()); // check outside if position isValid + + if(!twoHex) return BattleHexArray::neighbouringTilesCache[position]; - } + + return BattleHexArray::getNeighbouringTilesDblWide(position, side); } BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const { - auto defenderHexes = battle::Unit::getHexes( + const BattleHexArray & defenderHexes = battle::Unit::getHexes( getPosition(), doubleWide(), unitSide()); @@ -126,25 +99,35 @@ bool Unit::coversPos(BattleHex pos) const return getPosition() == pos || (doubleWide() && (occupiedHex() == pos)); } -BattleHexArray Unit::getHexes() const +const BattleHexArray & Unit::getHexes() const { return getHexes(getPosition(), doubleWide(), unitSide()); } -BattleHexArray Unit::getHexes(BattleHex assumedPos) const +const BattleHexArray & Unit::getHexes(BattleHex assumedPos) const { return getHexes(assumedPos, doubleWide(), unitSide()); } -BattleHexArray Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) +const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) { + static BattleHexArray::ArrayOfBattleHexArrays cache[4]; + int index = side == BattleSide::ATTACKER ? 0 : 2; + + if(!cache[index + twoHex][assumedPos].empty()) + return cache[index + twoHex][assumedPos]; + + // first run, initialize + BattleHexArray hexes; hexes.insert(assumedPos); if(twoHex) hexes.insert(occupiedHex(assumedPos, twoHex, side)); - return hexes; + cache[index + twoHex][assumedPos] = std::move(hexes); + + return cache[index + twoHex][assumedPos]; } BattleHex Unit::occupiedHex() const diff --git a/lib/battle/Unit.h b/lib/battle/Unit.h index e900eaae3..8973cca73 100644 --- a/lib/battle/Unit.h +++ b/lib/battle/Unit.h @@ -127,15 +127,15 @@ public: virtual std::string getDescription() const; - BattleHexArray getSurroundingHexes(BattleHex assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size + const BattleHexArray & getSurroundingHexes(BattleHex assumedPosition = BattleHex::INVALID) const; // get six or 8 surrounding hexes depending on creature size BattleHexArray getAttackableHexes(const Unit * attacker) const; - static BattleHexArray getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side); + static const BattleHexArray & getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side); bool coversPos(BattleHex position) const; //checks also if unit is double-wide - BattleHexArray getHexes() const; //up to two occupied hexes, starting from front - BattleHexArray getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front - static BattleHexArray getHexes(BattleHex assumedPos, bool twoHex, BattleSide side); + const BattleHexArray & getHexes() const; //up to two occupied hexes, starting from front + const BattleHexArray & getHexes(BattleHex assumedPos) const; //up to two occupied hexes, starting from front + static const BattleHexArray & getHexes(BattleHex assumedPos, bool twoHex, BattleSide side); BattleHex occupiedHex() const; //returns number of occupied hex (not the position) if stack is double wide; otherwise -1 BattleHex occupiedHex(BattleHex assumedPos) const; //returns number of occupied hex (not the position) if stack is double wide and would stand on assumedPos; otherwise -1 From 7a8edff419c0b322c8257611ac60f83b12bcecd1 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 5 Dec 2024 22:01:13 +0100 Subject: [PATCH 08/14] minor fixes --- client/battle/BattleFieldController.cpp | 11 ++++++----- client/battle/BattleFieldController.h | 6 +++--- client/mapView/MapRenderer.cpp | 20 ++++++++++---------- client/renderSDL/SDL_Extensions.cpp | 2 +- client/xBRZ/xbrz.cpp | 4 ++-- client/xBRZ/xbrz.h | 2 +- client/xBRZ/xbrz_tools.h | 8 ++++---- lib/battle/AccessibilityInfo.cpp | 2 +- lib/battle/BattleHexArray.h | 20 ++++++++++++++------ lib/battle/CBattleInfoCallback.cpp | 7 +++---- lib/bonuses/Limiters.cpp | 1 + 11 files changed, 46 insertions(+), 37 deletions(-) diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index cb23064ad..18bfa0489 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -442,7 +442,7 @@ BattleHexArray BattleFieldController::getRangeHexes(BattleHex sourceHex, uint8_t return rangeHexes; } -BattleHexArray BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray rangeHexes, uint8_t distanceToLimit) +BattleHexArray BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, const BattleHexArray & rangeHexes, uint8_t distanceToLimit) { BattleHexArray rangeLimitHexes; @@ -456,7 +456,7 @@ BattleHexArray BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, B return rangeLimitHexes; } -bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit) +bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, const BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit) { bool hexInRangeLimit = false; @@ -470,7 +470,8 @@ bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, BattleHexArray & ra return hexInRangeLimit; } -std::vector> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray wholeRangeHexes, BattleHexArray rangeLimitHexes) +std::vector> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes( + const BattleHexArray & wholeRangeHexes, const BattleHexArray & rangeLimitHexes) { std::vector> output; @@ -481,7 +482,7 @@ std::vector> BattleFieldController::getOutsideNeigh { // get all neighbours and their directions - auto neighbouringTiles = BattleHexArray::generateAllNeighbouringTiles(hex); + const BattleHexArray & neighbouringTiles = BattleHexArray::getAllNeighbouringTiles(hex); std::vector outsideNeighbourDirections; @@ -679,7 +680,7 @@ BattleHex BattleFieldController::getHexAtPosition(Point hoverPos) BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber) { const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide(); - auto neighbours = BattleHexArray::generateAllNeighbouringTiles(myNumber); + const BattleHexArray & neighbours = BattleHexArray::getAllNeighbouringTiles(myNumber); // 0 1 // 5 x 2 // 4 3 diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index e14aa5cab..8f21492df 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -68,13 +68,13 @@ class BattleFieldController : public CIntObject BattleHexArray getRangeHexes(BattleHex sourceHex, uint8_t distance); /// get only hexes at the limit of a range - BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray hexRange, uint8_t distanceToLimit); + BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, const BattleHexArray & hexRange, uint8_t distanceToLimit); /// calculate if a hex is in range limit and return its index in range - bool IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit); + bool IsHexInRangeLimit(BattleHex hex, const BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit); /// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists - std::vector> getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray rangeHexes, BattleHexArray rangeLimitHexes); + std::vector> getOutsideNeighbourDirectionsForLimitHexes(const BattleHexArray & rangeHexes, const BattleHexArray & rangeLimitHexes); /// calculates what image to use as range limit, depending on the direction of neighbours /// a mask is used internally to mark the directions of all neighbours diff --git a/client/mapView/MapRenderer.cpp b/client/mapView/MapRenderer.cpp index 4554a44bb..d3c0a88c7 100644 --- a/client/mapView/MapRenderer.cpp +++ b/client/mapView/MapRenderer.cpp @@ -34,14 +34,14 @@ #include "../../lib/mapping/CMapDefines.h" #include "../../lib/pathfinder/CGPathNode.h" -struct neighbourTilesInfo +struct NeighborTilesInfo { //567 //3 4 //012 std::bitset<8> d; - neighbourTilesInfo(IMapRendererContext & context, const int3 & pos) + NeighborTilesInfo(IMapRendererContext & context, const int3 & pos) { auto checkTile = [&](int dx, int dy) { @@ -340,9 +340,9 @@ void MapRendererFow::renderTile(IMapRendererContext & context, Canvas & target, { assert(!context.isVisible(coordinates)); - const neighbourTilesInfo neighbourInfo(context, coordinates); + const NeighborTilesInfo neighborInfo(context, coordinates); - int retBitmapID = neighbourInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide + int retBitmapID = neighborInfo.getBitmapID(); // >=0 -> partial hide, <0 - full hide if(retBitmapID < 0) { // generate a number that is predefined for each tile, @@ -367,8 +367,8 @@ uint8_t MapRendererFow::checksum(IMapRendererContext & context, const int3 & coo if (context.showSpellRange(coordinates)) return 0xff - 2; - const neighbourTilesInfo neighbourInfo(context, coordinates); - int retBitmapID = neighbourInfo.getBitmapID(); + const NeighborTilesInfo neighborInfo(context, coordinates); + int retBitmapID = neighborInfo.getBitmapID(); if(retBitmapID < 0) return 0xff - 1; return retBitmapID; @@ -738,9 +738,9 @@ MapRenderer::TileChecksum MapRenderer::getTileChecksum(IMapRendererContext & con return result; } - const neighbourTilesInfo neighbourInfo(context, coordinates); + const NeighborTilesInfo neighborInfo(context, coordinates); - if(!context.isVisible(coordinates) && neighbourInfo.areAllHidden()) + if(!context.isVisible(coordinates) && neighborInfo.areAllHidden()) { result[7] = rendererFow.checksum(context, coordinates); } @@ -769,9 +769,9 @@ void MapRenderer::renderTile(IMapRendererContext & context, Canvas & target, con return; } - const neighbourTilesInfo neighbourInfo(context, coordinates); + const NeighborTilesInfo neighborInfo(context, coordinates); - if(!context.isVisible(coordinates) && neighbourInfo.areAllHidden()) + if(!context.isVisible(coordinates) && neighborInfo.areAllHidden()) { rendererFow.renderTile(context, target, coordinates); } diff --git a/client/renderSDL/SDL_Extensions.cpp b/client/renderSDL/SDL_Extensions.cpp index 610c62f24..14671c2db 100644 --- a/client/renderSDL/SDL_Extensions.cpp +++ b/client/renderSDL/SDL_Extensions.cpp @@ -674,7 +674,7 @@ SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor switch (algorithm) { case EScalingAlgorithm::NEAREST: - xbrz::nearestneighbourScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); + xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); break; case EScalingAlgorithm::BILINEAR: xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h); diff --git a/client/xBRZ/xbrz.cpp b/client/xBRZ/xbrz.cpp index b6753de00..1eda5a46b 100644 --- a/client/xBRZ/xbrz.cpp +++ b/client/xBRZ/xbrz.cpp @@ -1273,10 +1273,10 @@ void xbrz::bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, } -void xbrz::nearestneighbourScale(const uint32_t* src, int srcWidth, int srcHeight, +void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, /**/ uint32_t* trg, int trgWidth, int trgHeight) { - nearestneighbourScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), 0, trgHeight, [](uint32_t pix) { return pix; }); } diff --git a/client/xBRZ/xbrz.h b/client/xBRZ/xbrz.h index 1643e41f1..4e646dba6 100644 --- a/client/xBRZ/xbrz.h +++ b/client/xBRZ/xbrz.h @@ -69,7 +69,7 @@ void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, /**/ uint32_t* trg, int trgWidth, int trgHeight); -void nearestneighbourScale(const uint32_t* src, int srcWidth, int srcHeight, +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, /**/ uint32_t* trg, int trgWidth, int trgHeight); diff --git a/client/xBRZ/xbrz_tools.h b/client/xBRZ/xbrz_tools.h index 204056a38..b8bb8aa0c 100644 --- a/client/xBRZ/xbrz_tools.h +++ b/client/xBRZ/xbrz_tools.h @@ -67,9 +67,9 @@ void fillBlock(Pix* trg, int pitch /*[bytes]*/, Pix col, int blockWidth, int blo } -//nearest-neighbour (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) +//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) template -void nearestneighbourScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, +void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/, int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) { @@ -104,9 +104,9 @@ void nearestneighbourScale(const PixSrc* src, int srcWidth, int srcHeight, int s } -//nearest-neighbour (going over source image - fast for upscaling, since source is read only once +//nearest-neighbor (going over source image - fast for upscaling, since source is read only once template -void nearestneighbourScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, +void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/, int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) { diff --git a/lib/battle/AccessibilityInfo.cpp b/lib/battle/AccessibilityInfo.cpp index e2f1eab15..43854b026 100644 --- a/lib/battle/AccessibilityInfo.cpp +++ b/lib/battle/AccessibilityInfo.cpp @@ -18,7 +18,7 @@ VCMI_LIB_NAMESPACE_BEGIN bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) const { //at(otherHex) != EAccessibility::ACCESSIBLE && (at(otherHex) != EAccessibility::GATE || side != BattleSide::DEFENDER) - auto accessibility = at(tile); + const auto & accessibility = at(tile); if(accessibility == EAccessibility::ALIVE_STACK) { diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 5c51198ae..f876100f6 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -63,16 +63,24 @@ public: /// returns all tiles, unavailable tiles will be set as invalid /// order of returned tiles matches EDir enum - static BattleHexArray generateAllNeighbouringTiles(BattleHex hex) + static BattleHexArray getAllNeighbouringTiles(BattleHex hex) { - BattleHexArray ret; + static ArrayOfBattleHexArrays cache; + static bool initialized = false; - ret.resize(6); + if(initialized) + return cache[hex.hex]; - for(auto dir : BattleHex::hexagonalDirections()) - ret.set(dir, hex.cloneInDirection(dir, false)); + for(BattleHex h = 0; h < GameConstants::BFIELD_SIZE; h.hex++) + { + cache[h].resize(6); - return ret; + for(auto dir : BattleHex::hexagonalDirections()) + cache[h].set(dir, h.cloneInDirection(dir, false)); + } + initialized = true; + + return cache[hex.hex]; } void checkAndPush(BattleHex tile) diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index c62cfba0e..ad21868d1 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -640,11 +640,11 @@ BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * { // Return true if given hex has at least one available neighbour. // Available hexes are already present in ret vector. - auto availableneighbour = boost::find_if(ret, [=] (BattleHex availableHex) + auto availableNeighbour = boost::find_if(ret, [=] (BattleHex availableHex) { return BattleHex::mutualPosition(hex, availableHex) >= 0; }); - return availableneighbour != ret.end(); + return availableNeighbour != ret.end(); }; for(const auto * otherSt : battleAliveUnits(otherSide(unit->unitSide()))) { @@ -1112,9 +1112,8 @@ bool CBattleInfoCallback::isInObstacle( const BattleHexArray & obstacleHexes, const ReachabilityInfo::Parameters & params) const { - const BattleHexArray & occupiedHexes = battle::Unit::getHexes(hex, params.doubleWide, params.side); - for(auto occupiedHex : occupiedHexes) + for(auto occupiedHex : battle::Unit::getHexes(hex, params.doubleWide, params.side)) { if(params.ignoreKnownAccessible && params.knownAccessible->contains(occupiedHex)) continue; diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 6243ec541..993782c47 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -220,6 +220,7 @@ ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &contex return ILimiter::EDecision::DISCARD; auto accept = false; + for (auto hex : stack->getHexes()) accept |= applicableHexes.contains(hex); From 794595184eb2782909116f8efd01ae09d0bd0b8c Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Fri, 6 Dec 2024 19:59:05 +0100 Subject: [PATCH 09/14] use class scope static instead of function scope --- lib/battle/BattleHexArray.cpp | 107 +++++++++++++--------------------- lib/battle/BattleHexArray.h | 15 ++++- lib/battle/Unit.cpp | 2 +- 3 files changed, 53 insertions(+), 71 deletions(-) diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index f7bd751e8..00927227b 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -74,6 +74,43 @@ BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::calculateNeighbouringTile return ret; } +BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::calculateNeighbouringTilesDblWide(BattleSide side) +{ + ArrayOfBattleHexArrays ret; + + for(BattleHex hex = 0; hex < GameConstants::BFIELD_SIZE; hex.hex++) + { + BattleHexArray hexes; + + if(side == BattleSide::ATTACKER) + { + const BattleHex otherHex = hex - 1; + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(hex.cloneInDirection(dir, false)); + + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + } + else if(side == BattleSide::DEFENDER) + { + const BattleHex otherHex = hex + 1; + + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); + + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false)); + } + ret[hex.hex] = std::move(hexes); + } + + return ret; +} + BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) { BattleHexArray ret; @@ -83,73 +120,6 @@ BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) return ret; } -const BattleHexArray & BattleHexArray::getNeighbouringTilesDblWide(BattleHex pos, BattleSide side) -{ - static std::array ret; // 2 -> only two valid sides: ATTACKER and DEFENDER - size_t sideIdx = static_cast(side); - static bool initialized[2] = { false, false }; - - if(!initialized[sideIdx]) - { - // first run, need to initialize - - for(BattleHex hex = 0; hex < GameConstants::BFIELD_SIZE; hex.hex++) - { - BattleHexArray hexes; - - if(side == BattleSide::ATTACKER) - { - const BattleHex otherHex = hex - 1; - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(hex.cloneInDirection(dir, false)); - - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - } - else if(side == BattleSide::DEFENDER) - { - const BattleHex otherHex = hex + 1; - - hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); - - hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false)); - } - ret[sideIdx][hex.hex] = std::move(hexes); - } - initialized[sideIdx] = true; - } - - return ret[sideIdx][pos.hex]; -} - -const BattleHexArray & BattleHexArray::getClosestTilesCache(BattleHex pos, BattleSide side) -{ - assert(!neighbouringTilesCache.empty()); - - static std::array ret; - static bool initialized = false; - size_t sideIdx = static_cast(side); - - if(!initialized) - { - ret[sideIdx].resize(GameConstants::BFIELD_SIZE); - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - ret[sideIdx].set(hex, neighbouringTilesCache[hex].getClosestTile(BattleSide::ATTACKER, hex)); - } - initialized = true; - } - - return ret[sideIdx]; -} - void BattleHexArray::merge(const BattleHexArray & other) noexcept { for(auto hex : other) @@ -177,5 +147,8 @@ void BattleHexArray::clear() noexcept } const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles(); +const std::map BattleHexArray::neighbouringTilesDblWide = + { { BattleSide::ATTACKER, calculateNeighbouringTilesDblWide(BattleSide::ATTACKER) }, + { BattleSide::DEFENDER, calculateNeighbouringTilesDblWide(BattleSide::DEFENDER) } }; VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index f876100f6..8b0a2f960 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -36,6 +36,7 @@ public: using ArrayOfBattleHexArrays = std::array; static const ArrayOfBattleHexArrays neighbouringTilesCache; + static const std::map neighbouringTilesDblWide; BattleHexArray() noexcept { @@ -136,13 +137,20 @@ public: return internalStorage.insert(pos, hex); } - static const BattleHexArray & getClosestTilesCache(BattleHex pos, BattleSide side); - static const BattleHexArray & getNeighbouringTilesDblWide(BattleHex pos, BattleSide side); - BattleHex getClosestTile(BattleSide side, BattleHex initialPos) const; void merge(const BattleHexArray & other) noexcept; + template >> + void merge(const Container & container) noexcept + { + for(auto value : container) + { + insert(value); + } + } + void clear() noexcept; inline void erase(size_type index) noexcept { @@ -306,6 +314,7 @@ private: /// returns all valid neighbouring tiles static BattleHexArray::ArrayOfBattleHexArrays calculateNeighbouringTiles(); + static BattleHexArray::ArrayOfBattleHexArrays calculateNeighbouringTilesDblWide(BattleSide side); static BattleHexArray generateNeighbouringTiles(BattleHex hex); }; diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index 18a634eef..ba16a1af7 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -65,7 +65,7 @@ const BattleHexArray & Unit::getSurroundingHexes(BattleHex position, bool twoHex if(!twoHex) return BattleHexArray::neighbouringTilesCache[position]; - return BattleHexArray::getNeighbouringTilesDblWide(position, side); + return BattleHexArray::neighbouringTilesDblWide.at(side).at(position); } BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const From 44a645b5e0c9613fd031db01b1fe44c150abaefe Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Fri, 13 Dec 2024 01:03:05 +0100 Subject: [PATCH 10/14] use bitset for presenceFlags and steady_clock for measurments --- AI/BattleAI/BattleEvaluator.cpp | 4 ++-- lib/battle/BattleHexArray.h | 2 +- lib/battle/Unit.cpp | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 408d4d96e..5ac5c5fd4 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -320,9 +320,9 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) return stack->waited() ? BattleAction::makeDefend(stack) : BattleAction::makeWait(stack); } -uint64_t timeElapsed(std::chrono::time_point start) +uint64_t timeElapsed(std::chrono::time_point start) { - auto end = std::chrono::high_resolution_clock::now(); + auto end = std::chrono::steady_clock::now(); return std::chrono::duration_cast(end - start).count(); } diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 8b0a2f960..27afd1e40 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -292,7 +292,7 @@ public: private: StorageType internalStorage; - std::array presenceFlags = {}; + std::bitset presenceFlags = {}; [[nodiscard]] inline bool BattleHexArray::isNotValidForInsertion(BattleHex hex) const { diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index ba16a1af7..dd4185354 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -111,13 +111,13 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos) const const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleSide side) { - static BattleHexArray::ArrayOfBattleHexArrays cache[4]; + static BattleHexArray::ArrayOfBattleHexArrays precomputed[4]; int index = side == BattleSide::ATTACKER ? 0 : 2; - if(!cache[index + twoHex][assumedPos].empty()) - return cache[index + twoHex][assumedPos]; + if(!precomputed[index + twoHex][assumedPos].empty()) + return precomputed[index + twoHex][assumedPos]; - // first run, initialize + // first run, compute BattleHexArray hexes; hexes.insert(assumedPos); @@ -125,9 +125,9 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleS if(twoHex) hexes.insert(occupiedHex(assumedPos, twoHex, side)); - cache[index + twoHex][assumedPos] = std::move(hexes); + precomputed[index + twoHex][assumedPos] = std::move(hexes); - return cache[index + twoHex][assumedPos]; + return precomputed[index + twoHex][assumedPos]; } BattleHex Unit::occupiedHex() const From ac8104d56d7bbe2e185c9fc5c313cdfbac9e4455 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 2 Jan 2025 12:11:18 +0100 Subject: [PATCH 11/14] SonarCloud recomendations. Code review follow-up: - Replace std::vector with boost::small_vector - Rename function merge to insert --- AI/BattleAI/BattleEvaluator.cpp | 2 +- AI/StupidAI/StupidAI.cpp | 2 +- AI/StupidAI/StupidAI.h | 2 +- client/CPlayerInterface.cpp | 2 +- client/CPlayerInterface.h | 2 +- client/battle/BattleAnimationClasses.cpp | 6 +-- client/battle/BattleAnimationClasses.h | 14 +++--- client/battle/BattleFieldController.cpp | 2 +- lib/CGameInterface.cpp | 2 +- lib/CGameInterface.h | 2 +- lib/IGameEventsReceiver.h | 2 +- lib/battle/BattleHex.h | 5 +- lib/battle/BattleHexArray.cpp | 2 +- lib/battle/BattleHexArray.h | 59 ++++++++++-------------- lib/battle/CBattleInfoCallback.cpp | 8 ++-- lib/battle/ReachabilityInfo.cpp | 4 +- lib/battle/Unit.cpp | 2 +- lib/bonuses/Limiters.h | 2 +- lib/spells/effects/Moat.cpp | 4 +- 19 files changed, 56 insertions(+), 68 deletions(-) diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 5ac5c5fd4..7cb538a70 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -431,7 +431,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage(); if(triggerIsNegative) - obstacleHexes.merge(obst->getAffectedTiles()); + obstacleHexes.insert(obst->getAffectedTiles()); } } // Flying stack doesn't go hex by hex, so we can't backtrack using predecessors. diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 8ddab9b5c..6e25e8f44 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -247,7 +247,7 @@ void CStupidAI::battleNewRound(const BattleID & battleID) print("battleNewRound called"); } -void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) +void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport) { print("battleStackMoved called"); } diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index 06a0e04e8..0aa45e601 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -43,7 +43,7 @@ public: //void battleResultsApplied() override; //called when all effects of last battle are applied void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn - void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; + void battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport) override; void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks //void battleTriggerEffect(const BattleTriggerEffect & bte) override; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 0b8e1e333..5bbb30c91 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -846,7 +846,7 @@ void CPlayerInterface::battleLogMessage(const BattleID & battleID, const std::ve battleInt->displayBattleLog(lines); } -void CPlayerInterface::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) +void CPlayerInterface::battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport) { EVENT_HANDLER_CALLED_BY_CLIENT; BATTLE_EVENT_POSSIBLE_RETURN; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index ceed1d42e..3aefb35d9 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -154,7 +154,7 @@ protected: // Call-ins from server, should not be called directly, but only via void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; used for HP regen handling void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn void battleLogMessage(const BattleID & battleID, const std::vector & lines) override; - void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; + void battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport) override; void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; //called when a specific effect is set to stacks void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte) override; //various one-shot effect diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index 0441e7d2e..f87ba198b 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -422,7 +422,7 @@ MovementAnimation::~MovementAnimation() CCS->soundh->stopSound(moveSoundHandler); } -MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, BattleHexArray _destTiles, int _distance) +MovementAnimation::MovementAnimation(BattleInterface & owner, const CStack *stack, const BattleHexArray & _destTiles, int _distance) : StackMoveAnimation(owner, stack, stack->getPosition(), _destTiles.front()), destTiles(_destTiles), currentMoveIndex(0), @@ -892,10 +892,10 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & logAnim->debug("CPointEffectAnimation::init: effect %s", animationName.getName()); } -EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHexArray hex, int effects, bool reversed): +EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, const BattleHexArray & hexes, int effects, bool reversed): EffectAnimation(owner, animationName, effects, 1.0f, reversed) { - battlehexes = hex; + battlehexes = hexes; } EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, float transparencyFactor, bool reversed): diff --git a/client/battle/BattleAnimationClasses.h b/client/battle/BattleAnimationClasses.h index a00ac82c3..1314c2350 100644 --- a/client/battle/BattleAnimationClasses.h +++ b/client/battle/BattleAnimationClasses.h @@ -143,7 +143,7 @@ class MovementAnimation : public StackMoveAnimation private: int moveSoundHandler; // sound handler used when moving a unit - BattleHexArray destTiles; //full path, includes already passed hexes + const BattleHexArray & destTiles; //full path, includes already passed hexes ui32 currentMoveIndex; // index of nextHex in destTiles double begX, begY; // starting position @@ -159,7 +159,7 @@ public: bool init() override; void tick(uint32_t msPassed) override; - MovementAnimation(BattleInterface & owner, const CStack *_stack, BattleHexArray _destTiles, int _distance); + MovementAnimation(BattleInterface & owner, const CStack *_stack, const BattleHexArray & _destTiles, int _distance); ~MovementAnimation(); }; @@ -339,14 +339,14 @@ public: EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, float transparencyFactor = 1.f, bool reversed = false); /// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos , int effects = 0, bool reversed = false); - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector pos , int effects = 0, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos , int effects = 0, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector pos , int effects = 0, bool reversed = false); /// Create animation positioned at certain hex(es) - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex , int effects = 0, float transparencyFactor = 1.0f, bool reversed = false); - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHexArray hex, int effects = 0, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex , int effects = 0, float transparencyFactor = 1.0f, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, const BattleHexArray & hexes, int effects = 0, bool reversed = false); - EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects = 0, bool reversed = false); + EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects = 0, bool reversed = false); ~EffectAnimation(); bool init() override; diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 18bfa0489..4d2f3203a 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -281,7 +281,7 @@ void BattleFieldController::redrawBackgroundWithHexes() if(settings["battle"]["stackRange"].Bool()) { BattleHexArray hexesToShade = occupiableHexes; - hexesToShade.merge(attackableHexes); + hexesToShade.insert(attackableHexes); for(BattleHex hex : hexesToShade) { showHighlightedHex(*backgroundWithHexes, cellShade, hex, false); diff --git a/lib/CGameInterface.cpp b/lib/CGameInterface.cpp index 852e56748..e19a7b012 100644 --- a/lib/CGameInterface.cpp +++ b/lib/CGameInterface.cpp @@ -204,7 +204,7 @@ void CAdventureAI::battleObstaclesChanged(const BattleID & battleID, const std:: battleAI->battleObstaclesChanged(battleID, obstacles); } -void CAdventureAI::battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) +void CAdventureAI::battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport) { battleAI->battleStackMoved(battleID, stack, dest, distance, teleport); } diff --git a/lib/CGameInterface.h b/lib/CGameInterface.h index fc1559bda..9719801d7 100644 --- a/lib/CGameInterface.h +++ b/lib/CGameInterface.h @@ -151,7 +151,7 @@ public: void actionFinished(const BattleID & battleID, const BattleAction &action) override; void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; void battleObstaclesChanged(const BattleID & battleID, const std::vector & obstacles) override; - void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; + void battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport) override; void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index e90e665f6..3f58ecc88 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -63,7 +63,7 @@ public: virtual void battleNewRoundFirst(const BattleID & battleID){}; //called at the beginning of each turn before changes are applied; virtual void battleNewRound(const BattleID & battleID){}; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn virtual void battleLogMessage(const BattleID & battleID, const std::vector & lines){}; - virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport){}; + virtual void battleStackMoved(const BattleID & battleID, const CStack * stack, const BattleHexArray & dest, int distance, bool teleport){}; virtual void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc){}; virtual void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse){};//called when a specific effect is set to stacks virtual void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte){}; //called for various one-shot effects diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index 4751d7515..6cf44fc40 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -97,9 +97,8 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f int y1 = hex1.getY(); int y2 = hex2.getY(); - // FIXME: why there was * 0.5 instead of / 2? - int x1 = static_cast(hex1.getX() + y1 / 2); - int x2 = static_cast(hex2.getX() + y2 / 2); + int x1 = hex1.getX() + y1 / 2; + int x2 = hex2.getX() + y2 / 2; int xDst = x2 - x1; int yDst = y2 - y1; diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index 00927227b..c53090955 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -120,7 +120,7 @@ BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) return ret; } -void BattleHexArray::merge(const BattleHexArray & other) noexcept +void BattleHexArray::insert(const BattleHexArray & other) noexcept { for(auto hex : other) { diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 27afd1e40..2d8c18da2 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -11,6 +11,7 @@ #pragma once #include "BattleHex.h" +#include VCMI_LIB_NAMESPACE_BEGIN @@ -19,7 +20,7 @@ class DLL_LINKAGE BattleHexArray { public: static constexpr uint8_t totalSize = GameConstants::BFIELD_SIZE; - using StorageType = std::vector; + using StorageType = boost::container::small_vector; using value_type = BattleHex; using size_type = StorageType::size_type; @@ -38,10 +39,7 @@ public: static const ArrayOfBattleHexArrays neighbouringTilesCache; static const std::map neighbouringTilesDblWide; - BattleHexArray() noexcept - { - internalStorage.reserve(totalSize); - } + BattleHexArray() = default; template >> @@ -95,9 +93,6 @@ public: void insert(BattleHex hex) noexcept { - /*if(isNotValidForInsertion(hex)) - return;*/ - if(contains(hex)) return; @@ -107,9 +102,6 @@ public: void set(size_type index, BattleHex hex) { - /*if(isNotValidForInsertion(hex)) - return;*/ - if(index >= internalStorage.size()) { logGlobal->error("Invalid BattleHexArray::set index parameter. It is " + std::to_string(index) @@ -125,11 +117,8 @@ public: internalStorage[index] = hex; } - iterator BattleHexArray::insert(iterator pos, BattleHex hex) noexcept + iterator insert(iterator pos, BattleHex hex) noexcept { - /*if(isNotValidForInsertion(hex)) - return pos;*/ - if(contains(hex)) return pos; @@ -139,11 +128,11 @@ public: BattleHex getClosestTile(BattleSide side, BattleHex initialPos) const; - void merge(const BattleHexArray & other) noexcept; + void insert(const BattleHexArray & other) noexcept; template >> - void merge(const Container & container) noexcept + void insert(const Container & container) noexcept { for(auto value : container) { @@ -167,7 +156,7 @@ public: inline std::vector toVector() const noexcept { - return internalStorage; + return std::vector(internalStorage.begin(), internalStorage.end()); } template @@ -196,7 +185,7 @@ public: return filtered; } - [[nodiscard]] inline bool BattleHexArray::contains(BattleHex hex) const noexcept + [[nodiscard]] inline bool contains(BattleHex hex) const noexcept { if(hex.isValid()) return presenceFlags[hex]; @@ -220,22 +209,22 @@ public: } } - [[nodiscard]] inline const BattleHex & BattleHexArray::back() const noexcept + [[nodiscard]] inline const BattleHex & back() const noexcept { return internalStorage.back(); } - [[nodiscard]] inline const BattleHex & BattleHexArray::front() const noexcept + [[nodiscard]] inline const BattleHex & front() const noexcept { return internalStorage.front(); } - [[nodiscard]] inline const BattleHex & BattleHexArray::operator[](size_type index) const noexcept + [[nodiscard]] inline const BattleHex & operator[](size_type index) const noexcept { return internalStorage[index]; } - [[nodiscard]] inline const BattleHex & BattleHexArray::at(size_type index) const + [[nodiscard]] inline const BattleHex & at(size_type index) const { return internalStorage.at(index); } @@ -245,47 +234,47 @@ public: return internalStorage.size(); } - [[nodiscard]] inline BattleHexArray::iterator BattleHexArray::begin() noexcept + [[nodiscard]] inline iterator begin() noexcept { return internalStorage.begin(); } - [[nodiscard]] inline BattleHexArray::const_iterator BattleHexArray::begin() const noexcept + [[nodiscard]] inline const_iterator begin() const noexcept { return internalStorage.begin(); } - [[nodiscard]] inline bool BattleHexArray::empty() const noexcept + [[nodiscard]] inline bool empty() const noexcept { return internalStorage.empty(); } - [[nodiscard]] inline BattleHexArray::iterator BattleHexArray::end() noexcept + [[nodiscard]] inline iterator end() noexcept { return internalStorage.end(); } - [[nodiscard]] inline BattleHexArray::const_iterator BattleHexArray::end() const noexcept + [[nodiscard]] inline const_iterator end() const noexcept { return internalStorage.end(); } - [[nodiscard]] inline BattleHexArray::reverse_iterator BattleHexArray::rbegin() noexcept + [[nodiscard]] inline reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } - [[nodiscard]] inline BattleHexArray::const_reverse_iterator BattleHexArray::rbegin() const noexcept + [[nodiscard]] inline const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } - [[nodiscard]] inline BattleHexArray::reverse_iterator BattleHexArray::rend() noexcept + [[nodiscard]] inline reverse_iterator rend() noexcept { return reverse_iterator(begin()); } - [[nodiscard]] inline BattleHexArray::const_reverse_iterator BattleHexArray::rend() const noexcept + [[nodiscard]] inline const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } @@ -294,7 +283,7 @@ private: StorageType internalStorage; std::bitset presenceFlags = {}; - [[nodiscard]] inline bool BattleHexArray::isNotValidForInsertion(BattleHex hex) const + [[nodiscard]] inline bool isNotValidForInsertion(BattleHex hex) const { if(isTower(hex)) return true; @@ -313,8 +302,8 @@ private: } /// returns all valid neighbouring tiles - static BattleHexArray::ArrayOfBattleHexArrays calculateNeighbouringTiles(); - static BattleHexArray::ArrayOfBattleHexArrays calculateNeighbouringTilesDblWide(BattleSide side); + static ArrayOfBattleHexArrays calculateNeighbouringTiles(); + static ArrayOfBattleHexArrays calculateNeighbouringTilesDblWide(BattleSide side); static BattleHexArray generateNeighbouringTiles(BattleHex hex); }; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index ad21868d1..83ca6d781 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -630,7 +630,7 @@ BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * for(auto hex : ret) occupiable.insert(unit->occupiedHex(hex)); - ret.merge(occupiable); + ret.insert(occupiable); } @@ -655,7 +655,7 @@ BattleHexArray CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * if(battleCanShoot(unit, otherSt->getPosition())) { - attackable->merge(occupied); + attackable->insert(occupied); continue; } @@ -1336,7 +1336,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( } if(attacker->hasBonusOfType(BonusType::ATTACKS_ALL_ADJACENT)) { - at.hostileCreaturePositions.merge(attacker->getSurroundingHexes(attackerPos)); + at.hostileCreaturePositions.insert(attacker->getSurroundingHexes(attackerPos)); } if(attacker->hasBonusOfType(BonusType::THREE_HEADED_ATTACK)) { @@ -1428,7 +1428,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle:: if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::neighbouringTilesCache[attackerPos].contains(destinationTile)) { - at.hostileCreaturePositions.merge(BattleHexArray::neighbouringTilesCache[destinationTile]); + at.hostileCreaturePositions.insert(BattleHexArray::neighbouringTilesCache[destinationTile]); at.hostileCreaturePositions.insert(destinationTile); } diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index 069c9a5cf..cb36f9973 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -71,11 +71,11 @@ uint32_t ReachabilityInfo::distToNearestNeighbour( { // It can be back to back attack o==o or head to head =oo=. // In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles - attackableHexes.merge(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); + attackableHexes.insert(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); } else { - attackableHexes.merge(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); + attackableHexes.insert(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); } } diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index dd4185354..a1f3e86ed 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -88,7 +88,7 @@ BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const hexes.pop_back(); for(auto hex : hexes) - targetableHexes.merge(BattleHexArray::neighbouringTilesCache[hex]); + targetableHexes.insert(BattleHexArray::neighbouringTilesCache[hex]); } return targetableHexes; diff --git a/lib/bonuses/Limiters.h b/lib/bonuses/Limiters.h index b525a153a..bafa4f7b0 100644 --- a/lib/bonuses/Limiters.h +++ b/lib/bonuses/Limiters.h @@ -10,7 +10,7 @@ #include "Bonus.h" -#include "../battle/BattleHex.h" +#include "../battle/BattleHexArray.h" #include "../serializer/Serializeable.h" #include "../constants/Enumerations.h" diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index e32db09a0..e8568df1c 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -103,7 +103,7 @@ void Moat::convertBonus(const Mechanics * m, std::vector & converted) con BattleHexArray flatMoatHexes; for(const auto & moatPatch : moatHexes) - flatMoatHexes.merge(moatPatch); + flatMoatHexes.insert(moatPatch); nb.limiter = std::make_shared(std::move(flatMoatHexes)); converted.push_back(nb); @@ -168,7 +168,7 @@ void Moat::placeObstacles(ServerCallback * server, const Mechanics * m, const Ef obstacle.appearSound = sideOptions.appearSound; //For dispellable moats obstacle.appearAnimation = sideOptions.appearAnimation; //For dispellable moats obstacle.animation = sideOptions.animation; - obstacle.customSize.merge(destination); + obstacle.customSize.insert(destination); obstacle.animationYOffset = sideOptions.offsetY; pack.changes.emplace_back(); obstacle.toInfo(pack.changes.back()); From dad64376618a3e38bf918f25050154a24cfd0449 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Thu, 2 Jan 2025 23:56:04 +0100 Subject: [PATCH 12/14] Refactor BattleHex, remake the use of precomputed neighbouring tiles containers. - Moved short, frequently used functions to the BattleHex header for inlining - Made BattleHex a class with a private hex value - Moved getClosestTile implementation back to BattleHex - Enabled access to static precomputed data in BattleHexArray via BattleHex (note: circular dependency prevented static precomputed containers being directly placed in BattleHex) --- AI/BattleAI/AttackPossibility.cpp | 2 +- AI/BattleAI/BattleEvaluator.cpp | 6 +- AI/BattleAI/BattleExchangeVariant.cpp | 4 +- AI/StupidAI/StupidAI.cpp | 3 +- client/battle/BattleFieldController.cpp | 8 +- client/battle/BattleInterface.h | 2 +- client/battle/BattleObstacleController.h | 2 +- client/battle/BattleSiegeController.cpp | 2 +- client/battle/BattleStacksController.h | 2 +- lib/battle/AccessibilityInfo.cpp | 2 +- lib/battle/BattleHex.cpp | 147 +++++------------- lib/battle/BattleHex.h | 170 +++++++++++++++++---- lib/battle/BattleHexArray.cpp | 182 ++++++++++------------- lib/battle/BattleHexArray.h | 65 ++++---- lib/battle/BattleInfo.cpp | 2 +- lib/battle/CBattleInfoCallback.cpp | 24 +-- lib/battle/Destination.h | 2 +- lib/battle/ReachabilityInfo.cpp | 2 +- lib/battle/Unit.cpp | 8 +- lib/networkPacks/PacksForClientBattle.h | 2 +- lib/spells/BattleSpellMechanics.cpp | 2 +- lib/spells/CSpellHandler.h | 2 - lib/spells/effects/Effect.h | 2 +- lib/spells/effects/LocationEffect.cpp | 1 + lib/spells/effects/UnitEffect.cpp | 2 +- server/battles/BattleActionProcessor.h | 2 +- server/battles/BattleFlowProcessor.h | 2 +- 27 files changed, 338 insertions(+), 312 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index 1c25b0512..171cee8a7 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -384,7 +384,7 @@ AttackPossibility AttackPossibility::evaluate( affectedUnits = defenderUnits; vstd::concatenate(affectedUnits, retaliatedUnits); - logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex.hex, defHex.hex); + logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex, defHex); std::map> defenderStates; diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 7cb538a70..2acca4485 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -215,7 +215,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) bestAttack.affectedUnits[0]->unitType()->getJsonKey(), bestAttack.affectedUnits[0]->getCount(), (int)bestAttack.from, - (int)bestAttack.attack.attacker->getPosition().hex, + (int)bestAttack.attack.attacker->getPosition(), bestAttack.attack.chargeDistance, bestAttack.attack.attacker->getMovementRange(0), bestAttack.defenderDamageReduce, @@ -252,7 +252,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) if(siegeDefense) { - logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition().hex); + logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition()); BattleAttackInfo bai(stack, stack, 0, false); AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai); @@ -286,7 +286,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) "Moving %s towards hex %s[%d], score: %2f", stack->getDescription(), moveTarget.cachedAttack->attack.defender->getDescription(), - moveTarget.cachedAttack->attack.defender->getPosition().hex, + moveTarget.cachedAttack->attack.defender->getPosition(), moveTarget.score); return goTowardsNearest(stack, moveTarget.positions, *targets); diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 66fe316f4..9c491b412 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -960,7 +960,7 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) { - for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[hex.hex]) + for(BattleHex neighbour : hex.getNeighbouringTiles()) { reachable = unitReachability.distances.at(neighbour) <= radius; @@ -1021,7 +1021,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb if(hexStack && cb->battleMatchOwner(unit, hexStack, false)) { enemyUnit = true; - for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[hex.hex]) + for(BattleHex neighbour : hex.getNeighbouringTiles()) { reachable = unitReachability.distances.at(neighbour) <= unitSpeed; diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 6e25e8f44..631169143 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -107,7 +107,8 @@ static bool willSecondHexBlockMoreEnemyShooters(std::shared_ptr for(int i = 0; i < 2; i++) { - for (auto neighbour : BattleHexArray::neighbouringTilesCache[i ? h2.hex : h1.hex]) + BattleHex hex = i ? h2 : h1; + for (auto neighbour : hex.getNeighbouringTiles()) if(const auto * s = cb->getBattle(battleID)->battleGetUnitByPos(neighbour)) if(s->isShooter()) shooters[i]++; diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 4d2f3203a..2eb9d8c9d 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -482,7 +482,7 @@ std::vector> BattleFieldController::getOutsideNeigh { // get all neighbours and their directions - const BattleHexArray & neighbouringTiles = BattleHexArray::getAllNeighbouringTiles(hex); + const BattleHexArray & neighbouringTiles = hex.getAllNeighbouringTiles(); std::vector outsideNeighbourDirections; @@ -492,9 +492,7 @@ std::vector> BattleFieldController::getOutsideNeigh if(!neighbouringTiles[direction].isAvailable()) continue; - auto it = std::find(wholeRangeHexes.begin(), wholeRangeHexes.end(), neighbouringTiles[direction]); - - if(it == wholeRangeHexes.end()) + if(!wholeRangeHexes.contains(neighbouringTiles[direction])) outsideNeighbourDirections.push_back(BattleHex::EDir(direction)); // push direction } @@ -680,7 +678,7 @@ BattleHex BattleFieldController::getHexAtPosition(Point hoverPos) BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber) { const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide(); - const BattleHexArray & neighbours = BattleHexArray::getAllNeighbouringTiles(myNumber); + const BattleHexArray & neighbours = myNumber.getAllNeighbouringTiles(); // 0 1 // 5 x 2 // 4 3 diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index 43a529fd4..a5a2fe538 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -10,6 +10,7 @@ #pragma once #include "BattleConstants.h" +#include "../lib/battle/BattleHex.h" #include "../gui/CIntObject.h" #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation #include "../ConditionalWait.h" @@ -27,7 +28,6 @@ class BattleAction; class CGTownInstance; struct CatapultAttack; struct BattleTriggerEffect; -struct BattleHex; struct InfoAboutHero; class ObstacleChanges; class CPlayerBattleCallback; diff --git a/client/battle/BattleObstacleController.h b/client/battle/BattleObstacleController.h index 39119cf32..48a7ebfcd 100644 --- a/client/battle/BattleObstacleController.h +++ b/client/battle/BattleObstacleController.h @@ -13,7 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN -struct BattleHex; +class BattleHex; struct CObstacleInstance; class JsonNode; class ObstacleChanges; diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index 2f3c4df5e..cde4aa5c1 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -195,7 +195,7 @@ const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) co return town->fortificationsLevel().lowerTowerShooter.toCreature(); } - throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.hex)); + throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position)); } Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index f13f9fca4..6e79bdd4e 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -13,7 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN -struct BattleHex; +class BattleHex; class BattleHexArray; class BattleAction; class CStack; diff --git a/lib/battle/AccessibilityInfo.cpp b/lib/battle/AccessibilityInfo.cpp index 43854b026..efb90549a 100644 --- a/lib/battle/AccessibilityInfo.cpp +++ b/lib/battle/AccessibilityInfo.cpp @@ -25,7 +25,7 @@ bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) if(!destructibleEnemyTurns) return false; - return destructibleEnemyTurns->at(tile.hex) >= 0; + return destructibleEnemyTurns->at(tile) >= 0; } if(accessibility != EAccessibility::ACCESSIBLE) diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index 7e21c4937..1da5de6f8 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -9,130 +9,63 @@ */ #include "StdInc.h" #include "BattleHex.h" +#include "BattleHexArray.h" -VCMI_LIB_NAMESPACE_BEGIN - -BattleHex::BattleHex() : hex(INVALID) {} - -BattleHex::BattleHex(si16 _hex) : hex(_hex) {} - -BattleHex::BattleHex(si16 x, si16 y) +VCMI_LIB_NAMESPACE_BEGIN + +BattleHex BattleHex::getClosestTile(const BattleHexArray & hexes, BattleSide side, BattleHex initialPos) { - setXY(x, y); -} + if(hexes.empty()) + return BattleHex(); -BattleHex::BattleHex(std::pair xy) -{ - setXY(xy); -} + BattleHex initialHex = BattleHex(initialPos); + int closestDistance = std::numeric_limits::max(); + BattleHexArray closestTiles; -BattleHex::operator si16() const -{ - return hex; -} - -void BattleHex::setX(si16 x) -{ - setXY(x, getY()); -} - -void BattleHex::setY(si16 y) -{ - setXY(getX(), y); -} - -void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) -{ - if(hasToBeValid) + for(auto hex : hexes) { - if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) - throw std::runtime_error("Valid hex required"); + int distance = initialHex.getDistance(initialHex, hex); + if(distance < closestDistance) + { + closestDistance = distance; + closestTiles.clear(); + closestTiles.insert(hex); + } + else if(distance == closestDistance) + closestTiles.insert(hex); } - hex = x + y * GameConstants::BFIELD_WIDTH; -} - -void BattleHex::setXY(std::pair xy) -{ - setXY(xy.first, xy.second); -} - -si16 BattleHex::getX() const -{ - return hex % GameConstants::BFIELD_WIDTH; -} - -si16 BattleHex::getY() const -{ - return hex / GameConstants::BFIELD_WIDTH; -} - -std::pair BattleHex::getXY() const -{ - return std::make_pair(getX(), getY()); -} - -BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid) -{ - si16 x = getX(); - si16 y = getY(); - switch(dir) + auto compareHorizontal = [side, initialPos](const BattleHex & left, const BattleHex & right) { - case TOP_LEFT: - setXY((y%2) ? x-1 : x, y-1, hasToBeValid); - break; - case TOP_RIGHT: - setXY((y%2) ? x : x+1, y-1, hasToBeValid); - break; - case RIGHT: - setXY(x+1, y, hasToBeValid); - break; - case BOTTOM_RIGHT: - setXY((y%2) ? x : x+1, y+1, hasToBeValid); - break; - case BOTTOM_LEFT: - setXY((y%2) ? x-1 : x, y+1, hasToBeValid); - break; - case LEFT: - setXY(x-1, y, hasToBeValid); - break; - case NONE: - break; - default: - throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); - break; - } - return *this; + if(left.getX() != right.getX()) + { + return (side == BattleSide::ATTACKER) ? (left.getX() > right.getX()) : (left.getX() < right.getX()); + } + return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); + }; + + auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal); + return (bestTile != closestTiles.end()) ? *bestTile : BattleHex(); +} + +const BattleHexArray & BattleHex::getAllNeighbouringTiles() const +{ + return BattleHexArray::getAllNeighbouringTiles(*this); } -BattleHex & BattleHex::operator+=(BattleHex::EDir dir) +const BattleHexArray & BattleHex::getNeighbouringTiles() const { - return moveInDirection(dir); + return BattleHexArray::getNeighbouringTiles(*this); } -BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const +const BattleHexArray & BattleHex::getNeighbouringTilesDblWide(BattleSide side) const { - BattleHex result(hex); - result.moveInDirection(dir, hasToBeValid); - return result; -} - -BattleHex BattleHex::operator+(BattleHex::EDir dir) const -{ - return cloneInDirection(dir); -} - -BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) -{ - for(auto dir : hexagonalDirections()) - if(hex2 == hex1.cloneInDirection(dir, false)) - return dir; - return NONE; -} + return BattleHexArray::getNeighbouringTilesDblWide(*this, side); +} std::ostream & operator<<(std::ostream & os, const BattleHex & hex) { - return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); + return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % static_cast(hex)); } VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index 6cf44fc40..e2226399a 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -22,9 +22,13 @@ namespace GameConstants const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; } +class BattleHexArray; + // for battle stacks' positions -struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design +class DLL_LINKAGE BattleHex { +public: + // helpers for siege static constexpr si16 CASTLE_CENTRAL_TOWER = -2; static constexpr si16 CASTLE_BOTTOM_TOWER = -3; @@ -46,8 +50,8 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f static constexpr si16 GATE_OUTER = 95; static constexpr si16 GATE_INNER = 96; - si16 hex; static constexpr si16 INVALID = -1; + enum EDir { NONE = -1, @@ -64,11 +68,25 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f BOTTOM }; - BattleHex(); - BattleHex(si16 _hex); - BattleHex(si16 x, si16 y); - BattleHex(std::pair xy); - operator si16() const; + BattleHex() + : hex(INVALID) + {} + BattleHex(si16 _hex) + : hex(_hex) + {} + BattleHex(si16 x, si16 y) + { + setXY(x, y); + } + BattleHex(std::pair xy) + { + setXY(xy); + } + operator si16() const + { + return hex; + } + inline bool isValid() const { return hex >= 0 && hex < GameConstants::BFIELD_SIZE; @@ -79,19 +97,97 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; } - void setX(si16 x); - void setY(si16 y); - void setXY(si16 x, si16 y, bool hasToBeValid = true); - void setXY(std::pair xy); - si16 getX() const; - si16 getY() const; - std::pair getXY() const; - BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); - BattleHex& operator+=(EDir dir); - BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const; - BattleHex operator+(EDir dir) const; + void setX(si16 x) + { + setXY(x, getY()); + } + + void setY(si16 y) + { + setXY(getX(), y); + } + + void setXY(si16 x, si16 y, bool hasToBeValid = true) + { + if(hasToBeValid) + { + if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) + throw std::runtime_error("Valid hex required"); + } + + hex = x + y * GameConstants::BFIELD_WIDTH; + } + + void setXY(std::pair xy) + { + setXY(xy.first, xy.second); + } + + si16 getX() const + { + return hex % GameConstants::BFIELD_WIDTH; + } + + si16 getY() const + { + return hex / GameConstants::BFIELD_WIDTH; + } + + std::pair getXY() const + { + return std::make_pair(getX(), getY()); + } + + BattleHex & moveInDirection(EDir dir, bool hasToBeValid = true) + { + si16 x = getX(); + si16 y = getY(); + switch(dir) + { + case TOP_LEFT: + setXY((y % 2) ? x - 1 : x, y - 1, hasToBeValid); + break; + case TOP_RIGHT: + setXY((y % 2) ? x : x + 1, y - 1, hasToBeValid); + break; + case RIGHT: + setXY(x + 1, y, hasToBeValid); + break; + case BOTTOM_RIGHT: + setXY((y % 2) ? x : x + 1, y + 1, hasToBeValid); + break; + case BOTTOM_LEFT: + setXY((y % 2) ? x - 1 : x, y + 1, hasToBeValid); + break; + case LEFT: + setXY(x - 1, y, hasToBeValid); + break; + case NONE: + break; + default: + throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); + break; + } + return *this; + } + + BattleHex & operator+=(EDir dir) + { + return moveInDirection(dir); + } + + BattleHex operator+(EDir dir) const + { + return cloneInDirection(dir); + } + + BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const + { + BattleHex result(hex); + result.moveInDirection(dir, hasToBeValid); + return result; + } - static EDir mutualPosition(BattleHex hex1, BattleHex hex2); static uint8_t getDistance(BattleHex hex1, BattleHex hex2) { int y1 = hex1.getY(); @@ -108,17 +204,41 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f return std::abs(xDst) + std::abs(yDst); } - + + static BattleHex getClosestTile(const BattleHexArray & hexes, BattleSide side, BattleHex initialPos); + + //Constexpr defined array with all directions used in battle + static constexpr auto hexagonalDirections() + { + return std::array{TOP_LEFT, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT}; + } + + static EDir mutualPosition(BattleHex hex1, BattleHex hex2) + { + for(auto dir : hexagonalDirections()) + if(hex2 == hex1.cloneInDirection(dir, false)) + return dir; + return NONE; + } + + /// get (precomputed) all possible surrounding tiles + const BattleHexArray & getAllNeighbouringTiles() const; + + /// get (precomputed) only valid and available surrounding tiles + const BattleHexArray & getNeighbouringTiles() const; + + /// get (precomputed) only valid and available surrounding tiles for double wide creatures + const BattleHexArray & getNeighbouringTilesDblWide(BattleSide side) const; + template - void serialize(Handler &h) + void serialize(Handler & h) { h & hex; } - //Constexpr defined array with all directions used in battle - static constexpr auto hexagonalDirections() { - return std::array{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; - } +private: + + si16 hex; }; DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index c53090955..baddf6bf0 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -22,104 +22,6 @@ BattleHexArray::BattleHexArray(std::initializer_list initList) noexce } } -BattleHex BattleHexArray::getClosestTile(BattleSide side, BattleHex initialPos) const -{ - if(this->empty()) - return BattleHex(); - - BattleHex initialHex = BattleHex(initialPos); - int closestDistance = std::numeric_limits::max(); - BattleHexArray closestTiles; - - for(auto hex : internalStorage) - { - int distance = initialHex.getDistance(initialHex, hex); - if(distance < closestDistance) - { - closestDistance = distance; - closestTiles.clear(); - closestTiles.insert(hex); - } - else if(distance == closestDistance) - closestTiles.insert(hex); - } - - auto compareHorizontal = [side, initialPos](const BattleHex & left, const BattleHex & right) - { - if(left.getX() != right.getX()) - { - return (side == BattleSide::ATTACKER) ? (left.getX() > right.getX()) : (left.getX() < right.getX()); - } - return std::abs(left.getY() - initialPos.getY()) < std::abs(right.getY() - initialPos.getY()); - }; - - auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal); - return (bestTile != closestTiles.end()) ? *bestTile : BattleHex(); -} - -BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::calculateNeighbouringTiles() -{ - BattleHexArray::ArrayOfBattleHexArrays ret; - - for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) - { - BattleHexArray hexes = BattleHexArray::generateNeighbouringTiles(hex); - - size_t index = 0; - ret[hex].resize(hexes.size()); - for(auto neighbour : hexes) - ret[hex].set(index++, neighbour); - } - - return ret; -} - -BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::calculateNeighbouringTilesDblWide(BattleSide side) -{ - ArrayOfBattleHexArrays ret; - - for(BattleHex hex = 0; hex < GameConstants::BFIELD_SIZE; hex.hex++) - { - BattleHexArray hexes; - - if(side == BattleSide::ATTACKER) - { - const BattleHex otherHex = hex - 1; - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(hex.cloneInDirection(dir, false)); - - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); - hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - } - else if(side == BattleSide::DEFENDER) - { - const BattleHex otherHex = hex + 1; - - hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); - - for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) - hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); - - hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); - hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false)); - } - ret[hex.hex] = std::move(hexes); - } - - return ret; -} - -BattleHexArray BattleHexArray::generateNeighbouringTiles(BattleHex hex) -{ - BattleHexArray ret; - for(auto dir : BattleHex::hexagonalDirections()) - ret.checkAndPush(hex.cloneInDirection(dir, false)); - - return ret; -} - void BattleHexArray::insert(const BattleHexArray & other) noexcept { for(auto hex : other) @@ -146,9 +48,85 @@ void BattleHexArray::clear() noexcept internalStorage.clear(); } -const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTilesCache = calculateNeighbouringTiles(); -const std::map BattleHexArray::neighbouringTilesDblWide = - { { BattleSide::ATTACKER, calculateNeighbouringTilesDblWide(BattleSide::ATTACKER) }, - { BattleSide::DEFENDER, calculateNeighbouringTilesDblWide(BattleSide::DEFENDER) } }; +BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTiles() +{ + BattleHexArray::ArrayOfBattleHexArrays ret; + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + BattleHexArray hexes; + + for(auto dir : BattleHex::hexagonalDirections()) + hexes.checkAndPush(BattleHex(hex).cloneInDirection(dir, false)); + + size_t index = 0; + ret[hex].resize(hexes.size()); + for(auto neighbour : hexes) + ret[hex].set(index++, neighbour); + } + + return ret; +} + +BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateAllNeighbouringTiles() +{ + ArrayOfBattleHexArrays ret; + + for(si16 hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) + { + ret[hex].resize(6); + + for(auto dir : BattleHex::hexagonalDirections()) + ret[hex].set(dir, BattleHex(hex).cloneInDirection(dir, false)); + } + + return ret; +} + +BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTilesDblWide(BattleSide side) +{ + ArrayOfBattleHexArrays ret; + + for(si16 h = 0; h < GameConstants::BFIELD_SIZE; h++) + { + BattleHexArray hexes; + BattleHex hex(h); + + if(side == BattleSide::ATTACKER) + { + const BattleHex otherHex = h - 1; + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(hex.cloneInDirection(dir, false)); + + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::LEFT, false)); + hexes.checkAndPush(otherHex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + } + else if(side == BattleSide::DEFENDER) + { + const BattleHex otherHex = h + 1; + + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::TOP_LEFT, false)); + + for(auto dir = static_cast(0); dir <= static_cast(4); dir = static_cast(dir + 1)) + hexes.checkAndPush(otherHex.cloneInDirection(dir, false)); + + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::BOTTOM_LEFT, false)); + hexes.checkAndPush(hex.cloneInDirection(BattleHex::EDir::LEFT, false)); + } + ret[h] = std::move(hexes); + } + + return ret; +} + +const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTiles = precalculateNeighbouringTiles(); +const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::allNeighbouringTiles = precalculateAllNeighbouringTiles(); +const std::map BattleHexArray::neighbouringTilesDblWide = + { + { BattleSide::ATTACKER, precalculateNeighbouringTilesDblWide(BattleSide::ATTACKER) }, + { BattleSide::DEFENDER, precalculateNeighbouringTilesDblWide(BattleSide::DEFENDER) } + }; VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 2d8c18da2..13d5bd75a 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -21,6 +21,7 @@ class DLL_LINKAGE BattleHexArray public: static constexpr uint8_t totalSize = GameConstants::BFIELD_SIZE; using StorageType = boost::container::small_vector; + using ArrayOfBattleHexArrays = std::array; using value_type = BattleHex; using size_type = StorageType::size_type; @@ -34,11 +35,6 @@ public: using reverse_iterator = typename StorageType::reverse_iterator; using const_reverse_iterator = typename StorageType::const_reverse_iterator; - using ArrayOfBattleHexArrays = std::array; - - static const ArrayOfBattleHexArrays neighbouringTilesCache; - static const std::map neighbouringTilesDblWide; - BattleHexArray() = default; template initList) noexcept; - /// returns all tiles, unavailable tiles will be set as invalid - /// order of returned tiles matches EDir enum - static BattleHexArray getAllNeighbouringTiles(BattleHex hex) - { - static ArrayOfBattleHexArrays cache; - static bool initialized = false; - - if(initialized) - return cache[hex.hex]; - - for(BattleHex h = 0; h < GameConstants::BFIELD_SIZE; h.hex++) - { - cache[h].resize(6); - - for(auto dir : BattleHex::hexagonalDirections()) - cache[h].set(dir, h.cloneInDirection(dir, false)); - } - initialized = true; - - return cache[hex.hex]; - } - void checkAndPush(BattleHex tile) { if(tile.isAvailable() && !contains(tile)) @@ -126,8 +100,6 @@ public: return internalStorage.insert(pos, hex); } - BattleHex getClosestTile(BattleSide side, BattleHex initialPos) const; - void insert(const BattleHexArray & other) noexcept; template neighbouringTilesDblWide; + + static ArrayOfBattleHexArrays precalculateNeighbouringTiles(); + static ArrayOfBattleHexArrays precalculateAllNeighbouringTiles(); + static ArrayOfBattleHexArrays precalculateNeighbouringTilesDblWide(BattleSide side); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index 4a91d649e..c28165df5 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -709,7 +709,7 @@ void BattleInfo::setUnitState(uint32_t id, const JsonNode & data, int64_t health if(!accessibility.accessible(changedStack->getPosition(), changedStack)) { - logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition().hex); + logNetwork->error("Cannot resurrect %s because hex %d is occupied!", changedStack->nodeName(), changedStack->getPosition()); return; //position is already occupied } } diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 83ca6d781..396a9fc98 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -176,7 +176,7 @@ bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const { if (!from.isAvailable() || !dest.isAvailable()) - throw std::runtime_error("Invalid hex (" + std::to_string(from.hex) + " and " + std::to_string(dest.hex) + ") received in battleHasPenaltyOnLine!" ); + throw std::runtime_error("Invalid hex (" + std::to_string(from) + " and " + std::to_string(dest) + ") received in battleHasPenaltyOnLine!" ); auto isTileBlocked = [&](BattleHex tile) { @@ -204,7 +204,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, while (next != dest) { - next = BattleHexArray::neighbouringTilesCache[next].getClosestTile(direction, dest); + next = BattleHex::getClosestTile(next.getNeighbouringTiles(), direction, dest); ret.insert(next); } assert(!ret.empty()); @@ -1077,9 +1077,9 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib if(isInObstacle(curHex, obstacles, checkParams)) continue; - const int costToNeighbour = ret.distances.at(curHex.hex) + 1; + const int costToNeighbour = ret.distances.at(curHex) + 1; - for(BattleHex neighbour : BattleHexArray::neighbouringTilesCache[curHex.hex]) + for(BattleHex neighbour : curHex.getNeighbouringTiles()) { auto additionalCost = 0; @@ -1093,13 +1093,13 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib } } - const int costFoundSoFar = ret.distances[neighbour.hex]; + const int costFoundSoFar = ret.distances[neighbour]; - if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar) + if(accessibleCache[neighbour] && costToNeighbour + additionalCost < costFoundSoFar) { hexq.push(neighbour); - ret.distances[neighbour.hex] = costToNeighbour + additionalCost; - ret.predecessors[neighbour.hex] = curHex; + ret.distances[neighbour] = costToNeighbour + additionalCost; + ret.predecessors[neighbour] = curHex; } } } @@ -1222,7 +1222,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS return BattleHex::INVALID; //all tiles are covered } - return occupyable.getClosestTile(side, pos); + return BattleHex::getClosestTile(occupyable, side, pos); } si8 CBattleInfoCallback::battleGetTacticDist() const @@ -1353,7 +1353,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( } if(attacker->hasBonusOfType(BonusType::WIDE_BREATH)) { - BattleHexArray hexes = BattleHexArray::neighbouringTilesCache[destinationTile]; + BattleHexArray hexes = destinationTile.getNeighbouringTiles(); for(int i = 0; i < hexes.size(); i++) { if(hexes.at(i) == attackOriginHex) @@ -1426,9 +1426,9 @@ AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle:: AttackableTiles at; RETURN_IF_NOT_BATTLE(at); - if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !BattleHexArray::neighbouringTilesCache[attackerPos].contains(destinationTile)) + if(attacker->hasBonusOfType(BonusType::SHOOTS_ALL_ADJACENT) && !attackerPos.getNeighbouringTiles().contains(destinationTile)) { - at.hostileCreaturePositions.insert(BattleHexArray::neighbouringTilesCache[destinationTile]); + at.hostileCreaturePositions.insert(destinationTile.getNeighbouringTiles()); at.hostileCreaturePositions.insert(destinationTile); } diff --git a/lib/battle/Destination.h b/lib/battle/Destination.h index 25c7d9f97..2dc0a450c 100644 --- a/lib/battle/Destination.h +++ b/lib/battle/Destination.h @@ -10,7 +10,7 @@ #pragma once -#include "BattleHexArray.h" +#include "BattleHex.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index cb36f9973..b16496bce 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -44,7 +44,7 @@ uint32_t ReachabilityInfo::distToNearestNeighbour( for(auto targetHex : targetHexes) { - for(auto & n : BattleHexArray::neighbouringTilesCache[targetHex]) + for(auto & n : targetHex.getNeighbouringTiles()) { if(distances[n] < ret) { diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index a1f3e86ed..fdb97d4d4 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -60,12 +60,10 @@ const BattleHexArray & Unit::getSurroundingHexes(BattleHex assumedPosition) cons const BattleHexArray & Unit::getSurroundingHexes(BattleHex position, bool twoHex, BattleSide side) { - assert(position.isValid()); // check outside if position isValid - if(!twoHex) - return BattleHexArray::neighbouringTilesCache[position]; + return position.getNeighbouringTiles(); - return BattleHexArray::neighbouringTilesDblWide.at(side).at(position); + return position.getNeighbouringTilesDblWide(side); } BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const @@ -88,7 +86,7 @@ BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const hexes.pop_back(); for(auto hex : hexes) - targetableHexes.insert(BattleHexArray::neighbouringTilesCache[hex]); + targetableHexes.insert(hex.getNeighbouringTiles()); } return targetableHexes; diff --git a/lib/networkPacks/PacksForClientBattle.h b/lib/networkPacks/PacksForClientBattle.h index eacd574ef..916bcd857 100644 --- a/lib/networkPacks/PacksForClientBattle.h +++ b/lib/networkPacks/PacksForClientBattle.h @@ -11,6 +11,7 @@ #include "NetPacksBase.h" #include "BattleChanges.h" +#include "../battle/BattleHexArray.h" #include "../battle/BattleAction.h" #include "../texts/MetaString.h" @@ -22,7 +23,6 @@ class CGHeroInstance; class CArmedInstance; class IBattleState; class BattleInfo; -class BattleHexArray; struct DLL_LINKAGE BattleStart : public CPackForClient { diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 5a66b4331..e448d78ec 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -610,7 +610,7 @@ std::vector BattleSpellMechanics::getPossibleDestinations(size_t in { hexesToCheck.insert(stack->getPosition()); - for(auto adjacent : BattleHexArray::neighbouringTilesCache[stack->getPosition().hex]) + for(auto adjacent : stack->getPosition().getNeighbouringTiles()) hexesToCheck.insert(adjacent); } diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index 24ddb7846..8b82a2eb3 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -16,8 +16,6 @@ #include "../IHandlerBase.h" #include "../ConstTransitivePtr.h" #include "../int3.h" -#include "../GameConstants.h" -#include "../battle/BattleHexArray.h" #include "../bonuses/Bonus.h" #include "../filesystem/ResourcePath.h" #include "../json/JsonNode.h" diff --git a/lib/spells/effects/Effect.h b/lib/spells/effects/Effect.h index 8cb5f446c..721b8ada0 100644 --- a/lib/spells/effects/Effect.h +++ b/lib/spells/effects/Effect.h @@ -14,7 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN -struct BattleHex; +class BattleHex; class BattleHexArray; class CBattleInfoCallback; class JsonSerializeFormat; diff --git a/lib/spells/effects/LocationEffect.cpp b/lib/spells/effects/LocationEffect.cpp index a1b1c3419..94b391b0b 100644 --- a/lib/spells/effects/LocationEffect.cpp +++ b/lib/spells/effects/LocationEffect.cpp @@ -11,6 +11,7 @@ #include "LocationEffect.h" #include "../ISpellMechanics.h" +#include "battle/BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/effects/UnitEffect.cpp b/lib/spells/effects/UnitEffect.cpp index 9a1f028e2..30e88a949 100644 --- a/lib/spells/effects/UnitEffect.cpp +++ b/lib/spells/effects/UnitEffect.cpp @@ -228,7 +228,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe if(possibleHexes.empty()) break; - destHex = possibleHexes.getClosestTile(unit->unitSide(), destHex); + destHex = BattleHex::getClosestTile(possibleHexes, unit->unitSide(), destHex); } return effectTarget; diff --git a/server/battles/BattleActionProcessor.h b/server/battles/BattleActionProcessor.h index 72744dc96..538f93da4 100644 --- a/server/battles/BattleActionProcessor.h +++ b/server/battles/BattleActionProcessor.h @@ -16,7 +16,7 @@ struct BattleLogMessage; struct BattleAttack; class BattleAction; class CBattleInfoCallback; -struct BattleHex; +class BattleHex; class CStack; class PlayerColor; enum class BonusType : uint8_t; diff --git a/server/battles/BattleFlowProcessor.h b/server/battles/BattleFlowProcessor.h index b628bbc6a..6c50af985 100644 --- a/server/battles/BattleFlowProcessor.h +++ b/server/battles/BattleFlowProcessor.h @@ -13,7 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN class CStack; -struct BattleHex; +class BattleHex; class BattleHexArray; class BattleAction; class CBattleInfoCallback; From 4031006317267e58e817e45cb798da31992ab016 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Fri, 3 Jan 2025 00:11:41 +0100 Subject: [PATCH 13/14] Drop battle elapsed time measurement, restore avHexes. Github validation report fixes. --- client/NetPacksClient.cpp | 12 ------------ lib/battle/BattleHex.cpp | 10 +++++----- lib/battle/BattleHex.h | 2 +- lib/battle/BattleHexArray.h | 12 ++++++++++-- lib/battle/CBattleInfoCallback.cpp | 7 ++++--- lib/spells/BattleSpellMechanics.cpp | 4 +--- lib/spells/effects/UnitEffect.cpp | 2 +- scripting/lua/LuaSpellEffect.cpp | 6 +++--- scripting/lua/LuaSpellEffect.h | 2 +- scripting/lua/api/BattleCb.cpp | 6 ++++-- .../lua/api/netpacks/BattleStackMoved.cpp | 2 +- test/battle/BattleHexTest.cpp | 18 +++++++++--------- test/battle/battle_UnitTest.cpp | 18 +++++++++--------- test/mock/mock_IBattleInfoCallback.h | 2 +- test/mock/mock_spells_Mechanics.h | 3 ++- test/scripting/LuaSpellEffectAPITest.cpp | 10 +++++----- test/scripting/LuaSpellEffectTest.cpp | 6 +++--- 17 files changed, 60 insertions(+), 62 deletions(-) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 35a7f86b5..9b6482b47 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -814,21 +814,11 @@ void ApplyClientNetPackVisitor::visitBattleNextRound(BattleNextRound & pack) callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleNewRound, pack.battleID); } -uint64_t timeElapsed(std::chrono::time_point start) -{ - auto end = std::chrono::high_resolution_clock::now(); - - return std::chrono::duration_cast(end - start).count(); -} - void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & pack) { if(!pack.askPlayerInterface) return; - auto start = std::chrono::steady_clock::now(); - static uint64_t duration = 0; - const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack); PlayerColor playerToCall; //pack.player that will move activated stack if(activated->isHypnotized()) @@ -843,8 +833,6 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & } cl.startPlayerBattleAction(pack.battleID, playerToCall); - duration += timeElapsed(start); - logGlobal->warn("Battle elapsed for %ld ms", duration); } void ApplyClientNetPackVisitor::visitBattleLogMessage(BattleLogMessage & pack) diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index 1da5de6f8..707af8b31 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -11,9 +11,9 @@ #include "BattleHex.h" #include "BattleHexArray.h" -VCMI_LIB_NAMESPACE_BEGIN - -BattleHex BattleHex::getClosestTile(const BattleHexArray & hexes, BattleSide side, BattleHex initialPos) +VCMI_LIB_NAMESPACE_BEGIN + +BattleHex BattleHex::getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes) { if(hexes.empty()) return BattleHex(); @@ -46,7 +46,7 @@ BattleHex BattleHex::getClosestTile(const BattleHexArray & hexes, BattleSide sid auto bestTile = std::min_element(closestTiles.begin(), closestTiles.end(), compareHorizontal); return (bestTile != closestTiles.end()) ? *bestTile : BattleHex(); -} +} const BattleHexArray & BattleHex::getAllNeighbouringTiles() const { @@ -61,7 +61,7 @@ const BattleHexArray & BattleHex::getNeighbouringTiles() const const BattleHexArray & BattleHex::getNeighbouringTilesDblWide(BattleSide side) const { return BattleHexArray::getNeighbouringTilesDblWide(*this, side); -} +} std::ostream & operator<<(std::ostream & os, const BattleHex & hex) { diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index e2226399a..6e7319fee 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -205,7 +205,7 @@ public: return std::abs(xDst) + std::abs(yDst); } - static BattleHex getClosestTile(const BattleHexArray & hexes, BattleSide side, BattleHex initialPos); + static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes); //Constexpr defined array with all directions used in battle static constexpr auto hexagonalDirections() diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index 13d5bd75a..b754b3420 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -176,7 +176,7 @@ public: /// get (precomputed) only valid and available surrounding tiles for double wide creatures static const BattleHexArray & getNeighbouringTilesDblWide(BattleHex hex, BattleSide side) { - assert(hex.isValid() && (side == BattleSide::ATTACKER || BattleSide::DEFENDER)); + assert(hex.isValid() && (side == BattleSide::ATTACKER || side == BattleSide::DEFENDER)); return neighbouringTilesDblWide.at(side)[hex]; } @@ -275,9 +275,17 @@ public: return const_reverse_iterator(begin()); } + bool operator ==(const BattleHexArray & other) const noexcept + { + if(internalStorage != other.internalStorage || presenceFlags != other.presenceFlags) + return false; + + return true; + } + private: StorageType internalStorage; - std::bitset presenceFlags = {}; + std::bitset presenceFlags; [[nodiscard]] inline bool isNotValidForInsertion(BattleHex hex) const { diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 396a9fc98..637e76396 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -204,7 +204,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, while (next != dest) { - next = BattleHex::getClosestTile(next.getNeighbouringTiles(), direction, dest); + next = BattleHex::getClosestTile(direction, dest, next.getNeighbouringTiles()); ret.insert(next); } assert(!ret.empty()); @@ -1159,6 +1159,7 @@ BattleHexArray CBattleInfoCallback::getStoppers(BattleSide whichSidePerspective) std::pair CBattleInfoCallback::getNearestStack(const battle::Unit * closest) const { auto reachability = getReachability(closest); + auto avHexes = battleGetAvailableHexes(reachability, closest, false); // I hate std::pairs with their undescriptive member names first / second struct DistStack @@ -1177,7 +1178,7 @@ std::pair CBattleInfoCallback::getNearestStack( for(const battle::Unit * st : possible) { - for(BattleHex hex : battleGetAvailableHexes(reachability, closest, false)) + for(BattleHex hex : avHexes) if(CStack::isMeleeAttackPossible(closest, st, hex)) { DistStack hlp = {reachability.distances[hex], hex, st}; @@ -1222,7 +1223,7 @@ BattleHex CBattleInfoCallback::getAvailableHex(const CreatureID & creID, BattleS return BattleHex::INVALID; //all tiles are covered } - return BattleHex::getClosestTile(occupyable, side, pos); + return BattleHex::getClosestTile(side, pos, occupyable); } si8 CBattleInfoCallback::battleGetTacticDist() const diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index e448d78ec..97ec2bbe0 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -609,9 +609,7 @@ std::vector BattleSpellMechanics::getPossibleDestinations(size_t in for(auto stack : stacks) { hexesToCheck.insert(stack->getPosition()); - - for(auto adjacent : stack->getPosition().getNeighbouringTiles()) - hexesToCheck.insert(adjacent); + hexesToCheck.insert(stack->getPosition().getNeighbouringTiles()); } for(auto hex : hexesToCheck) diff --git a/lib/spells/effects/UnitEffect.cpp b/lib/spells/effects/UnitEffect.cpp index 30e88a949..6704a5125 100644 --- a/lib/spells/effects/UnitEffect.cpp +++ b/lib/spells/effects/UnitEffect.cpp @@ -228,7 +228,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe if(possibleHexes.empty()) break; - destHex = BattleHex::getClosestTile(possibleHexes, unit->unitSide(), destHex); + destHex = BattleHex::getClosestTile(unit->unitSide(), destHex, possibleHexes); } return effectTarget; diff --git a/scripting/lua/LuaSpellEffect.cpp b/scripting/lua/LuaSpellEffect.cpp index 91db47955..89e9e362f 100644 --- a/scripting/lua/LuaSpellEffect.cpp +++ b/scripting/lua/LuaSpellEffect.cpp @@ -58,7 +58,7 @@ void LuaSpellEffect::adjustTargetTypes(std::vector & types) const } -void LuaSpellEffect::adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const +void LuaSpellEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const { } @@ -98,7 +98,7 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef for(const auto & dest : target) { JsonNode targetData; - targetData.Vector().emplace_back(dest.hexValue.hex); + targetData.Vector().emplace_back(static_cast(dest.hexValue)); if(dest.unitValue) targetData.Vector().emplace_back(dest.unitValue->unitId()); @@ -141,7 +141,7 @@ void LuaSpellEffect::apply(ServerCallback * server, const Mechanics * m, const E for(const auto & dest : target) { JsonNode targetData; - targetData.Vector().emplace_back(dest.hexValue.hex); + targetData.Vector().emplace_back(static_cast(dest.hexValue)); if(dest.unitValue) targetData.Vector().emplace_back(dest.unitValue->unitId()); diff --git a/scripting/lua/LuaSpellEffect.h b/scripting/lua/LuaSpellEffect.h index 54b11d4c2..5f793c8bf 100644 --- a/scripting/lua/LuaSpellEffect.h +++ b/scripting/lua/LuaSpellEffect.h @@ -49,7 +49,7 @@ public: void adjustTargetTypes(std::vector & types) const override; - void adjustAffectedHexes(std::set & hexes, const Mechanics * m, const Target & spellTarget) const override; + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; bool applicable(Problem & problem, const Mechanics * m) const override; bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; diff --git a/scripting/lua/api/BattleCb.cpp b/scripting/lua/api/BattleCb.cpp index de4e87a83..73942e018 100644 --- a/scripting/lua/api/BattleCb.cpp +++ b/scripting/lua/api/BattleCb.cpp @@ -99,11 +99,13 @@ int BattleCbProxy::getUnitByPos(lua_State * L) if(!S.tryGet(1, object)) return S.retVoid(); - BattleHex hex; + si16 hexVal; - if(!S.tryGet(2, hex.hex)) + if(!S.tryGet(2, hexVal)) return S.retNil(); + BattleHex hex(hexVal); + bool onlyAlive; if(!S.tryGet(3, onlyAlive)) diff --git a/scripting/lua/api/netpacks/BattleStackMoved.cpp b/scripting/lua/api/netpacks/BattleStackMoved.cpp index 888b1422e..ee7c6ce9c 100644 --- a/scripting/lua/api/netpacks/BattleStackMoved.cpp +++ b/scripting/lua/api/netpacks/BattleStackMoved.cpp @@ -47,7 +47,7 @@ int BattleStackMovedProxy::addTileToMove(lua_State * L) lua_Integer hex = 0; if(!S.tryGetInteger(2, hex)) return S.retVoid(); - object->tilesToMove.emplace_back(hex); + object->tilesToMove.insert(hex); return S.retVoid(); } diff --git a/test/battle/BattleHexTest.cpp b/test/battle/BattleHexTest.cpp index 075952bba..555f2bd0a 100644 --- a/test/battle/BattleHexTest.cpp +++ b/test/battle/BattleHexTest.cpp @@ -9,29 +9,29 @@ */ #include "StdInc.h" -#include "../lib/battle/BattleHex.h" +#include "../lib/battle/BattleHexArray.h" TEST(BattleHexTest, getNeighbouringTiles) { BattleHex mainHex; - std::vector neighbouringTiles; + BattleHexArray neighbouringTiles; mainHex.setXY(16,0); - neighbouringTiles = mainHex.neighbouringTiles(); + neighbouringTiles = mainHex.getNeighbouringTiles(); EXPECT_EQ(neighbouringTiles.size(), 1); mainHex.setXY(0,0); - neighbouringTiles = mainHex.neighbouringTiles(); + neighbouringTiles = mainHex.getNeighbouringTiles(); EXPECT_EQ(neighbouringTiles.size(), 2); mainHex.setXY(15,2); - neighbouringTiles = mainHex.neighbouringTiles(); + neighbouringTiles = mainHex.getNeighbouringTiles(); EXPECT_EQ(neighbouringTiles.size(), 3); mainHex.setXY(2,0); - neighbouringTiles = mainHex.neighbouringTiles(); + neighbouringTiles = mainHex.getNeighbouringTiles(); EXPECT_EQ(neighbouringTiles.size(), 4); mainHex.setXY(1,2); - neighbouringTiles = mainHex.neighbouringTiles(); + neighbouringTiles = mainHex.getNeighbouringTiles(); EXPECT_EQ(neighbouringTiles.size(), 5); mainHex.setXY(8,5); - neighbouringTiles = mainHex.neighbouringTiles(); + neighbouringTiles = mainHex.getNeighbouringTiles(); EXPECT_EQ(neighbouringTiles.size(), 6); ASSERT_TRUE(neighbouringTiles.size()==6 && mainHex==93); @@ -85,7 +85,7 @@ TEST(BattleHexTest, mutualPositions) TEST(BattleHexTest, getClosestTile) { BattleHex mainHex(0); - std::set possibilities; + BattleHexArray possibilities; possibilities.insert(3); possibilities.insert(170); possibilities.insert(100); diff --git a/test/battle/battle_UnitTest.cpp b/test/battle/battle_UnitTest.cpp index b6ead9ab2..556a5ab99 100644 --- a/test/battle/battle_UnitTest.cpp +++ b/test/battle/battle_UnitTest.cpp @@ -17,7 +17,7 @@ TEST(battle_Unit_getSurroundingHexes, oneWide) auto actual = battle::Unit::getSurroundingHexes(position, false, BattleSide::ATTACKER); - EXPECT_EQ(actual, position.neighbouringTiles()); + EXPECT_EQ(actual, position.getNeighbouringTiles()); } TEST(battle_Unit_getSurroundingHexes, oneWideLeftCorner) @@ -26,7 +26,7 @@ TEST(battle_Unit_getSurroundingHexes, oneWideLeftCorner) auto actual = battle::Unit::getSurroundingHexes(position, false, BattleSide::ATTACKER); - EXPECT_EQ(actual, position.neighbouringTiles()); + EXPECT_EQ(actual, position.getNeighbouringTiles()); } TEST(battle_Unit_getSurroundingHexes, oneWideRightCorner) @@ -35,7 +35,7 @@ TEST(battle_Unit_getSurroundingHexes, oneWideRightCorner) auto actual = battle::Unit::getSurroundingHexes(position, false, BattleSide::ATTACKER); - EXPECT_EQ(actual, position.neighbouringTiles()); + EXPECT_EQ(actual, position.getNeighbouringTiles()); } TEST(battle_Unit_getSurroundingHexes, doubleWideAttacker) @@ -44,7 +44,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideAttacker) auto actual = battle::Unit::getSurroundingHexes(position, true, BattleSide::ATTACKER); - static const std::vector expected = + static const BattleHexArray expected = { 60, 61, @@ -65,7 +65,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideLeftCorner) auto actualAtt = battle::Unit::getSurroundingHexes(position, true, BattleSide::ATTACKER); - static const std::vector expectedAtt = + static const BattleHexArray expectedAtt = { 35, 53, @@ -76,7 +76,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideLeftCorner) auto actualDef = battle::Unit::getSurroundingHexes(position, true, BattleSide::DEFENDER); - static const std::vector expectedDef = + static const BattleHexArray expectedDef = { 35, 36, @@ -94,7 +94,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideRightCorner) auto actualAtt = battle::Unit::getSurroundingHexes(position, true, BattleSide::ATTACKER); - static const std::vector expectedAtt = + static const BattleHexArray expectedAtt = { 116, 117, @@ -109,7 +109,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideRightCorner) auto actualDef = battle::Unit::getSurroundingHexes(position, true, BattleSide::DEFENDER); - static const std::vector expectedDef = + static const BattleHexArray expectedDef = { 116, 117, @@ -128,7 +128,7 @@ TEST(battle_Unit_getSurroundingHexes, doubleWideDefender) auto actual = battle::Unit::getSurroundingHexes(position, true, BattleSide::DEFENDER); - static const std::vector expected = + static const BattleHexArray expected = { 60, 61, diff --git a/test/mock/mock_IBattleInfoCallback.h b/test/mock/mock_IBattleInfoCallback.h index f2eb2b35b..63bfa2a9e 100644 --- a/test/mock/mock_IBattleInfoCallback.h +++ b/test/mock/mock_IBattleInfoCallback.h @@ -40,7 +40,7 @@ public: MOCK_CONST_METHOD0(getPlayerID, std::optional()); MOCK_CONST_METHOD2(battleGetAllObstaclesOnPos, std::vector>(BattleHex, bool)); - MOCK_CONST_METHOD2(getAllAffectedObstaclesByStack, std::vector>(const battle::Unit *, const std::set &)); + MOCK_CONST_METHOD2(getAllAffectedObstaclesByStack, std::vector>(const battle::Unit *, const BattleHexArray &)); }; diff --git a/test/mock/mock_spells_Mechanics.h b/test/mock/mock_spells_Mechanics.h index 288c06fa4..7660cfe72 100644 --- a/test/mock/mock_spells_Mechanics.h +++ b/test/mock/mock_spells_Mechanics.h @@ -12,6 +12,7 @@ #include "../../lib/spells/ISpellMechanics.h" #include "../../lib/CGameInfoCallback.h" +#include "../../lib/battle/BattleHexArray.h" namespace spells { @@ -22,7 +23,7 @@ public: MOCK_CONST_METHOD2(adaptProblem, bool(ESpellCastProblem, Problem &)); MOCK_CONST_METHOD1(adaptGenericProblem, bool(Problem &)); - MOCK_CONST_METHOD1(rangeInHexes, std::vector(BattleHex)); + MOCK_CONST_METHOD1(rangeInHexes, BattleHexArray(BattleHex)); MOCK_CONST_METHOD1(getAffectedStacks, std::vector(const Target &)); MOCK_CONST_METHOD1(canBeCast, bool(Problem &)); diff --git a/test/scripting/LuaSpellEffectAPITest.cpp b/test/scripting/LuaSpellEffectAPITest.cpp index f9c4a1b7d..dad0236e0 100644 --- a/test/scripting/LuaSpellEffectAPITest.cpp +++ b/test/scripting/LuaSpellEffectAPITest.cpp @@ -84,7 +84,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField) BattleHex hex(2,2); JsonNode first; - first.Vector().emplace_back(hex.hex); + first.Vector().emplace_back(static_cast(hex)); first.Vector().emplace_back(); JsonNode targets; @@ -113,7 +113,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnRightSideOfField) BattleHex hex(11,2); JsonNode first; - first.Vector().emplace_back(hex.hex); + first.Vector().emplace_back(static_cast(hex)); first.Vector().emplace_back(-1); JsonNode targets; @@ -138,13 +138,13 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit) BattleHex hex1(11,2); JsonNode unit; - unit.Vector().emplace_back(hex1.hex); + unit.Vector().emplace_back(static_cast(hex1)); unit.Vector().emplace_back(42); BattleHex hex2(5,4); JsonNode destination; - destination.Vector().emplace_back(hex2.hex); + destination.Vector().emplace_back(static_cast(hex2)); destination.Vector().emplace_back(-1); JsonNode targets; @@ -163,7 +163,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit) EXPECT_EQ(pack.teleporting, true); EXPECT_EQ(pack.distance, 0); - std::vector toMove(1, hex2); + BattleHexArray toMove = { hex2 }; EXPECT_EQ(pack.tilesToMove, toMove); }; diff --git a/test/scripting/LuaSpellEffectTest.cpp b/test/scripting/LuaSpellEffectTest.cpp index a550f57cc..0fac27fdf 100644 --- a/test/scripting/LuaSpellEffectTest.cpp +++ b/test/scripting/LuaSpellEffectTest.cpp @@ -154,11 +154,11 @@ TEST_F(LuaSpellEffectTest, ApplicableTargetRedirected) JsonNode first; - first.Vector().emplace_back(hex1.hex); + first.Vector().emplace_back(static_cast(hex1)); first.Vector().emplace_back(id1); JsonNode second; - second.Vector().emplace_back(hex2.hex); + second.Vector().emplace_back(static_cast(hex2)); second.Vector().emplace_back(-1); JsonNode targets; @@ -193,7 +193,7 @@ TEST_F(LuaSpellEffectTest, ApplyRedirected) subject->apply(&serverMock, &mechanicsMock, target); JsonNode first; - first.Vector().emplace_back(hex1.hex); + first.Vector().emplace_back(static_cast(hex1)); first.Vector().emplace_back(id1); JsonNode targets; From dbe82b94f6fdaf18de7eb642d5c016251681d079 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Mon, 6 Jan 2025 23:05:45 +0100 Subject: [PATCH 14/14] Changes following review: - shared_ptr for destructibleEnemyTurns instead of raw pointer - drop implicit int conversion for BattleHex class and implement toInt() instead - implement necessary operators in BattleHex - adjust code to work properly with JSON serializer --- AI/BattleAI/AttackPossibility.cpp | 2 +- AI/BattleAI/BattleEvaluator.cpp | 12 +-- AI/BattleAI/BattleEvaluator.h | 2 +- AI/BattleAI/BattleExchangeVariant.cpp | 29 ++++--- AI/BattleAI/PotentialTargets.cpp | 2 +- AI/StupidAI/StupidAI.cpp | 6 +- client/battle/BattleActionsController.cpp | 2 +- client/battle/BattleAnimationClasses.cpp | 2 +- client/battle/BattleFieldController.cpp | 2 +- client/battle/BattleSiegeController.cpp | 6 +- lib/CStack.cpp | 4 +- lib/ObstacleHandler.cpp | 2 +- lib/battle/AccessibilityInfo.cpp | 4 +- lib/battle/AccessibilityInfo.h | 3 +- lib/battle/BattleHex.cpp | 10 +-- lib/battle/BattleHex.h | 93 +++++++++++++++-------- lib/battle/BattleHexArray.cpp | 12 +-- lib/battle/BattleHexArray.h | 32 ++++---- lib/battle/BattleInfo.cpp | 4 +- lib/battle/CBattleInfoCallback.cpp | 74 +++++++++--------- lib/battle/CBattleInfoCallback.h | 2 +- lib/battle/CObstacleInstance.cpp | 10 ++- lib/battle/CUnitState.cpp | 4 +- lib/battle/DamageCalculator.cpp | 2 +- lib/battle/ReachabilityInfo.cpp | 6 +- lib/battle/ReachabilityInfo.h | 2 +- lib/battle/Unit.cpp | 20 ++--- lib/battle/Unit.h | 5 +- lib/bonuses/Limiters.cpp | 2 +- lib/spells/BattleSpellMechanics.cpp | 2 +- lib/spells/CSpellHandler.cpp | 9 ++- lib/spells/effects/Catapult.cpp | 4 +- lib/spells/effects/Clone.cpp | 2 +- lib/spells/effects/DemonSummon.cpp | 2 +- lib/spells/effects/Moat.cpp | 2 +- lib/spells/effects/UnitEffect.cpp | 2 +- scripting/lua/LuaSpellEffect.cpp | 4 +- test/scripting/LuaSpellEffectAPITest.cpp | 8 +- test/scripting/LuaSpellEffectTest.cpp | 6 +- 39 files changed, 223 insertions(+), 174 deletions(-) diff --git a/AI/BattleAI/AttackPossibility.cpp b/AI/BattleAI/AttackPossibility.cpp index 171cee8a7..bc14014c9 100644 --- a/AI/BattleAI/AttackPossibility.cpp +++ b/AI/BattleAI/AttackPossibility.cpp @@ -280,7 +280,7 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg( std::set checkedUnits; auto attacker = attackInfo.attacker; - const BattleHexArray & hexes = attacker->getSurroundingHexes(hex); + const auto & hexes = attacker->getSurroundingHexes(hex); for(BattleHex tile : hexes) { auto st = state->battleGetUnitByPos(tile, true); diff --git a/AI/BattleAI/BattleEvaluator.cpp b/AI/BattleAI/BattleEvaluator.cpp index 2acca4485..75f39baaa 100644 --- a/AI/BattleAI/BattleEvaluator.cpp +++ b/AI/BattleAI/BattleEvaluator.cpp @@ -214,8 +214,8 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack) bestAttack.attackerState->unitType()->getJsonKey(), bestAttack.affectedUnits[0]->unitType()->getJsonKey(), bestAttack.affectedUnits[0]->getCount(), - (int)bestAttack.from, - (int)bestAttack.attack.attacker->getPosition(), + bestAttack.from.toInt(), + bestAttack.attack.attacker->getPosition().toInt(), bestAttack.attack.chargeDistance, bestAttack.attack.attacker->getMovementRange(0), bestAttack.defenderDamageReduce, @@ -355,7 +355,7 @@ BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex, } } -BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexArray hexes, const PotentialTargets & targets) +BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, const BattleHexArray & hexes, const PotentialTargets & targets) { auto reachability = cb->getBattle(battleID)->getReachability(stack); auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false); @@ -387,12 +387,12 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool { - return reachability.distances[h1] < reachability.distances[h2]; + return reachability.distances[h1.toInt()] < reachability.distances[h2.toInt()]; }); BattleHex bestNeighbour = hexes.front(); - if(reachability.distances[bestNeighbour] > GameConstants::BFIELD_SIZE) + if(reachability.distances[bestNeighbour.toInt()] > GameConstants::BFIELD_SIZE) { logAi->trace("No reachable hexes."); return BattleAction::makeDefend(stack); @@ -468,7 +468,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, BattleHexAr return moveOrAttack(stack, currentDest, targets); } - currentDest = reachability.predecessors[currentDest]; + currentDest = reachability.predecessors[currentDest.toInt()]; } } diff --git a/AI/BattleAI/BattleEvaluator.h b/AI/BattleAI/BattleEvaluator.h index 62a6c22eb..01be3d9f7 100644 --- a/AI/BattleAI/BattleEvaluator.h +++ b/AI/BattleAI/BattleEvaluator.h @@ -51,7 +51,7 @@ public: bool attemptCastingSpell(const CStack * stack); bool canCastSpell(); std::optional findBestCreatureSpell(const CStack * stack); - BattleAction goTowardsNearest(const CStack * stack, BattleHexArray hexes, const PotentialTargets & targets); + BattleAction goTowardsNearest(const CStack * stack, const BattleHexArray & hexes, const PotentialTargets & targets); std::vector getBrokenWallMoatHexes() const; bool hasWorkingTowers() const; void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only diff --git a/AI/BattleAI/BattleExchangeVariant.cpp b/AI/BattleAI/BattleExchangeVariant.cpp index 9c491b412..49be0c470 100644 --- a/AI/BattleAI/BattleExchangeVariant.cpp +++ b/AI/BattleAI/BattleExchangeVariant.cpp @@ -310,7 +310,7 @@ ReachabilityInfo getReachabilityWithEnemyBypass( for(auto & hex : unit->getHexes()) if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns - params.destructibleEnemyTurns[hex] = turnsToKill * unit->getMovementRange(); + params.destructibleEnemyTurns[hex.toInt()] = turnsToKill * unit->getMovementRange(); } params.bypassEnemyStacks = true; @@ -415,11 +415,11 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( for(BattleHex enemyHex : enemy->getAttackableHexes(activeStack)) { - while(!flying && dists.distances[enemyHex] > speed && dists.predecessors.at(enemyHex).isValid()) + while(!flying && dists.distances[enemyHex.toInt()] > speed && dists.predecessors.at(enemyHex.toInt()).isValid()) { - enemyHex = dists.predecessors.at(enemyHex); + enemyHex = dists.predecessors.at(enemyHex.toInt()); - if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK) + if(dists.accessibility[enemyHex.toInt()] == EAccessibility::ALIVE_STACK) { auto defenderToBypass = hb->battleGetUnitByPos(enemyHex); @@ -429,7 +429,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( logAi->trace("Found target to bypass at %d", enemyHex.hex); #endif - auto attackHex = dists.predecessors[enemyHex]; + auto attackHex = dists.predecessors[enemyHex.toInt()]; auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack)); auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb); @@ -440,7 +440,7 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable( auto bypassScore = calculateExchange( attackBypass, - dists.distances[attackHex], + dists.distances[attackHex.toInt()], targets, damageCache, hb, @@ -916,8 +916,7 @@ void BattleExchangeEvaluator::updateReachabilityMap(std::shared_ptr BattleExchangeEvaluator::getOneTurnReachableUn ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit); - bool reachable = unitReachability.distances.at(hex) <= radius; + bool reachable = unitReachability.distances.at(hex.toInt()) <= radius; - if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK) + if(!reachable && unitReachability.accessibility[hex.toInt()] == EAccessibility::ALIVE_STACK) { const battle::Unit * hexStack = cb->battleGetUnitByPos(hex); @@ -962,7 +961,7 @@ std::vector BattleExchangeEvaluator::getOneTurnReachableUn { for(BattleHex neighbour : hex.getNeighbouringTiles()) { - reachable = unitReachability.distances.at(neighbour) <= radius; + reachable = unitReachability.distances.at(neighbour.toInt()) <= radius; if(reachable) break; } @@ -1009,12 +1008,12 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb auto unitReachability = turnBattle.getReachability(unit); auto unitSpeed = unit->getMovementRange(turn); // Cached value, to avoid performance hit - for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1) + for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex++) { bool enemyUnit = false; - bool reachable = unitReachability.distances.at(hex) <= unitSpeed; + bool reachable = unitReachability.distances.at(hex.toInt()) <= unitSpeed; - if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK) + if(!reachable && unitReachability.accessibility[hex.toInt()] == EAccessibility::ALIVE_STACK) { const battle::Unit * hexStack = turnBattle.battleGetUnitByPos(hex); @@ -1023,7 +1022,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb enemyUnit = true; for(BattleHex neighbour : hex.getNeighbouringTiles()) { - reachable = unitReachability.distances.at(neighbour) <= unitSpeed; + reachable = unitReachability.distances.at(neighbour.toInt()) <= unitSpeed; if(reachable) break; } diff --git a/AI/BattleAI/PotentialTargets.cpp b/AI/BattleAI/PotentialTargets.cpp index f38415ef7..6b7fbc874 100644 --- a/AI/BattleAI/PotentialTargets.cpp +++ b/AI/BattleAI/PotentialTargets.cpp @@ -50,7 +50,7 @@ PotentialTargets::PotentialTargets( auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility { - int distance = hex.isValid() ? reachability.distances[hex] : 0; + int distance = hex.isValid() ? reachability.distances[hex.toInt()] : 0; auto bai = BattleAttackInfo(attackerInfo, defender, distance, shooting); return AttackPossibility::evaluate(bai, hex, damageCache, state); diff --git a/AI/StupidAI/StupidAI.cpp b/AI/StupidAI/StupidAI.cpp index 631169143..36c131059 100644 --- a/AI/StupidAI/StupidAI.cpp +++ b/AI/StupidAI/StupidAI.cpp @@ -291,7 +291,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac std::sort(hexes.begin(), hexes.end(), [&](BattleHex h1, BattleHex h2) -> bool { - return reachability.distances[h1] < reachability.distances[h2]; + return reachability.distances[h1.toInt()] < reachability.distances[h2.toInt()]; }); for(auto hex : hexes) @@ -313,7 +313,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac BattleHex bestneighbour = hexes.front(); - if(reachability.distances[bestneighbour] > GameConstants::BFIELD_SIZE) + if(reachability.distances[bestneighbour.toInt()] > GameConstants::BFIELD_SIZE) { return BattleAction::makeDefend(stack); } @@ -347,7 +347,7 @@ BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stac return BattleAction::makeMove(stack, currentDest); } - currentDest = reachability.predecessors[currentDest]; + currentDest = reachability.predecessors[currentDest.toInt()]; } } } diff --git a/client/battle/BattleActionsController.cpp b/client/battle/BattleActionsController.cpp index 9cbd79965..54f8dbcd2 100644 --- a/client/battle/BattleActionsController.cpp +++ b/client/battle/BattleActionsController.cpp @@ -522,7 +522,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle { const auto * attacker = owner.stacksController->getActiveStack(); BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex); - int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex] : 0; + int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex.toInt()] : 0; DamageEstimation retaliation; BattleAttackInfo attackInfo(attacker, targetStack, distance, false ); DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation); diff --git a/client/battle/BattleAnimationClasses.cpp b/client/battle/BattleAnimationClasses.cpp index f87ba198b..196066d1f 100644 --- a/client/battle/BattleAnimationClasses.cpp +++ b/client/battle/BattleAnimationClasses.cpp @@ -174,7 +174,7 @@ AttackAnimation::AttackAnimation(BattleInterface & owner, const CStack *attacker attackingStack(attacker) { assert(attackingStack && "attackingStack is nullptr in CBattleAttack::CBattleAttack !\n"); - attackingStackPosBeforeReturn = attackingStack->getPosition(); + attackingStackPosBeforeReturn = attackingStack->getPosition().toInt(); } HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack) diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 2eb9d8c9d..6e6ad3731 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -839,7 +839,7 @@ void BattleFieldController::updateAccessibleHexes() bool BattleFieldController::stackCountOutsideHex(const BattleHex & number) const { - return stackCountOutsideHexes[number]; + return stackCountOutsideHexes[number.toInt()]; } void BattleFieldController::showAll(Canvas & to) diff --git a/client/battle/BattleSiegeController.cpp b/client/battle/BattleSiegeController.cpp index cde4aa5c1..e698b1aca 100644 --- a/client/battle/BattleSiegeController.cpp +++ b/client/battle/BattleSiegeController.cpp @@ -185,7 +185,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) const { - switch (position) + switch (position.toInt()) { case BattleHex::CASTLE_CENTRAL_TOWER: return town->fortificationsLevel().citadelShooter.toCreature(); @@ -195,14 +195,14 @@ const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) co return town->fortificationsLevel().lowerTowerShooter.toCreature(); } - throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position)); + throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.toInt())); } Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const { // Turret positions are read out of the config/wall_pos.txt int posID = 0; - switch (position) + switch (position.toInt()) { case BattleHex::CASTLE_CENTRAL_TOWER: // keep creature posID = EWallVisual::CREATURE_KEEP; diff --git a/lib/CStack.cpp b/lib/CStack.cpp index 9d36c9078..df2a38fa2 100644 --- a/lib/CStack.cpp +++ b/lib/CStack.cpp @@ -252,8 +252,8 @@ BattleHexArray CStack::meleeAttackHexes(const battle::Unit * attacker, const bat if (!defenderPos.isValid()) defenderPos = defender->getPosition(); - BattleHex otherAttackerPos = attackerPos + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1); - BattleHex otherDefenderPos = defenderPos + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1); + BattleHex otherAttackerPos = attackerPos.toInt() + (attacker->unitSide() == BattleSide::ATTACKER ? -1 : 1); + BattleHex otherDefenderPos = defenderPos.toInt() + (defender->unitSide() == BattleSide::ATTACKER ? -1 : 1); if(BattleHex::mutualPosition(attackerPos, defenderPos) >= 0) //front <=> front { diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index d21fba7ab..e8472ca9e 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -66,7 +66,7 @@ BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const BattleHexArray ret; for(int offset : blockedTiles) { - BattleHex toBlock = hex + offset; + BattleHex toBlock = hex.toInt() + offset; if((hex.getY() & 1) && !(toBlock.getY() & 1)) toBlock += BattleHex::LEFT; diff --git a/lib/battle/AccessibilityInfo.cpp b/lib/battle/AccessibilityInfo.cpp index efb90549a..21211392a 100644 --- a/lib/battle/AccessibilityInfo.cpp +++ b/lib/battle/AccessibilityInfo.cpp @@ -18,14 +18,14 @@ VCMI_LIB_NAMESPACE_BEGIN bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) const { //at(otherHex) != EAccessibility::ACCESSIBLE && (at(otherHex) != EAccessibility::GATE || side != BattleSide::DEFENDER) - const auto & accessibility = at(tile); + const auto & accessibility = at(tile.toInt()); if(accessibility == EAccessibility::ALIVE_STACK) { if(!destructibleEnemyTurns) return false; - return destructibleEnemyTurns->at(tile) >= 0; + return destructibleEnemyTurns->at(tile.toInt()) >= 0; } if(accessibility != EAccessibility::ACCESSIBLE) diff --git a/lib/battle/AccessibilityInfo.h b/lib/battle/AccessibilityInfo.h index a9fd9722d..8377d163b 100644 --- a/lib/battle/AccessibilityInfo.h +++ b/lib/battle/AccessibilityInfo.h @@ -32,10 +32,11 @@ enum class EAccessibility using TAccessibilityArray = std::array; +using TBattlefieldTurnsArray = std::array; struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray { - const std::array * destructibleEnemyTurns = nullptr; + std::shared_ptr destructibleEnemyTurns; //used only as a view for destructibleEnemyTurns from ReachabilityInfo::Parameters public: bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index 707af8b31..be7c8b51a 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -48,24 +48,24 @@ BattleHex BattleHex::getClosestTile(BattleSide side, BattleHex initialPos, const return (bestTile != closestTiles.end()) ? *bestTile : BattleHex(); } -const BattleHexArray & BattleHex::getAllNeighbouringTiles() const +const BattleHexArray & BattleHex::getAllNeighbouringTiles() const noexcept { return BattleHexArray::getAllNeighbouringTiles(*this); } -const BattleHexArray & BattleHex::getNeighbouringTiles() const +const BattleHexArray & BattleHex::getNeighbouringTiles() const noexcept { return BattleHexArray::getNeighbouringTiles(*this); } -const BattleHexArray & BattleHex::getNeighbouringTilesDblWide(BattleSide side) const +const BattleHexArray & BattleHex::getNeighbouringTilesDoubleWide(BattleSide side) const noexcept { - return BattleHexArray::getNeighbouringTilesDblWide(*this, side); + return BattleHexArray::getNeighbouringTilesDoubleWide(*this, side); } std::ostream & operator<<(std::ostream & os, const BattleHex & hex) { - return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % static_cast(hex)); + return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.toInt()); } VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index 6e7319fee..8ba2b7d3c 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -24,7 +24,8 @@ namespace GameConstants class BattleHexArray; -// for battle stacks' positions +// for battle stacks' positions; valid hexes are from 0 to 186; available are only those not in first and last column +// castle towers are -2, -3 and -4 class DLL_LINKAGE BattleHex { public: @@ -68,10 +69,10 @@ public: BOTTOM }; - BattleHex() + BattleHex() noexcept : hex(INVALID) {} - BattleHex(si16 _hex) + BattleHex(si16 _hex) noexcept : hex(_hex) {} BattleHex(si16 x, si16 y) @@ -82,17 +83,13 @@ public: { setXY(xy); } - operator si16() const - { - return hex; - } - inline bool isValid() const + [[nodiscard]] bool isValid() const noexcept { return hex >= 0 && hex < GameConstants::BFIELD_SIZE; } - bool isAvailable() const //valid position not in first or last column + [[nodiscard]] bool isAvailable() const noexcept //valid position not in first or last column { return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; } @@ -123,17 +120,17 @@ public: setXY(xy.first, xy.second); } - si16 getX() const + [[nodiscard]] si16 getX() const noexcept { return hex % GameConstants::BFIELD_WIDTH; } - si16 getY() const + [[nodiscard]] si16 getY() const noexcept { return hex / GameConstants::BFIELD_WIDTH; } - std::pair getXY() const + [[nodiscard]] std::pair getXY() const noexcept { return std::make_pair(getX(), getY()); } @@ -171,24 +168,14 @@ public: return *this; } - BattleHex & operator+=(EDir dir) - { - return moveInDirection(dir); - } - - BattleHex operator+(EDir dir) const - { - return cloneInDirection(dir); - } - - BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const + [[nodiscard]] BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const { BattleHex result(hex); result.moveInDirection(dir, hasToBeValid); return result; } - static uint8_t getDistance(BattleHex hex1, BattleHex hex2) + [[nodiscard]] static uint8_t getDistance(BattleHex hex1, BattleHex hex2) noexcept { int y1 = hex1.getY(); int y2 = hex2.getY(); @@ -205,15 +192,15 @@ public: return std::abs(xDst) + std::abs(yDst); } - static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes); + [[nodiscard]] static BattleHex getClosestTile(BattleSide side, BattleHex initialPos, const BattleHexArray & hexes); //Constexpr defined array with all directions used in battle - static constexpr auto hexagonalDirections() + [[nodiscard]] static constexpr auto hexagonalDirections() noexcept { return std::array{TOP_LEFT, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT, LEFT}; } - static EDir mutualPosition(BattleHex hex1, BattleHex hex2) + [[nodiscard]] static EDir mutualPosition(BattleHex hex1, BattleHex hex2) { for(auto dir : hexagonalDirections()) if(hex2 == hex1.cloneInDirection(dir, false)) @@ -222,13 +209,59 @@ public: } /// get (precomputed) all possible surrounding tiles - const BattleHexArray & getAllNeighbouringTiles() const; + [[nodiscard]] const BattleHexArray & getAllNeighbouringTiles() const noexcept; /// get (precomputed) only valid and available surrounding tiles - const BattleHexArray & getNeighbouringTiles() const; + [[nodiscard]] const BattleHexArray & getNeighbouringTiles() const noexcept; /// get (precomputed) only valid and available surrounding tiles for double wide creatures - const BattleHexArray & getNeighbouringTilesDblWide(BattleSide side) const; + [[nodiscard]] const BattleHexArray & getNeighbouringTilesDoubleWide(BattleSide side) const noexcept; + + /// get integer hex value + [[nodiscard]] si16 toInt() const noexcept + { + return hex; + } + + BattleHex & operator+=(EDir dir) + { + return moveInDirection(dir); + } + + [[nodiscard]] BattleHex operator+(EDir dir) const + { + return cloneInDirection(dir); + } + + // Prefix increment + BattleHex & operator++() noexcept + { + ++hex; + return *this; + } + + // Postfix increment + BattleHex operator++(int) noexcept + { + BattleHex temp = *this; + ++hex; + return temp; + } + + [[nodiscard]] bool operator ==(BattleHex other) const noexcept + { + return hex == other.hex; + } + + [[nodiscard]] bool operator !=(BattleHex other) const noexcept + { + return hex != other.hex; + } + + [[nodiscard]] bool operator <(BattleHex other) const noexcept + { + return hex < other.hex; + } template void serialize(Handler & h) diff --git a/lib/battle/BattleHexArray.cpp b/lib/battle/BattleHexArray.cpp index baddf6bf0..632329bf9 100644 --- a/lib/battle/BattleHexArray.cpp +++ b/lib/battle/BattleHexArray.cpp @@ -34,7 +34,7 @@ void BattleHexArray::erase(iterator first, iterator last) noexcept { for(auto it = first; it != last && it != internalStorage.end(); ++it) { - presenceFlags[*it] = 0; + presenceFlags[it->toInt()] = 0; } internalStorage.erase(first, last); @@ -43,7 +43,7 @@ void BattleHexArray::erase(iterator first, iterator last) noexcept void BattleHexArray::clear() noexcept { for(auto hex : internalStorage) - presenceFlags[hex] = 0; + presenceFlags[hex.toInt()] = 0; internalStorage.clear(); } @@ -83,7 +83,7 @@ BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateAllNeighbouri return ret; } -BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTilesDblWide(BattleSide side) +BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringTilesDoubleWide(BattleSide side) { ArrayOfBattleHexArrays ret; @@ -123,10 +123,10 @@ BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::precalculateNeighbouringT const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::neighbouringTiles = precalculateNeighbouringTiles(); const BattleHexArray::ArrayOfBattleHexArrays BattleHexArray::allNeighbouringTiles = precalculateAllNeighbouringTiles(); -const std::map BattleHexArray::neighbouringTilesDblWide = +const std::map BattleHexArray::neighbouringTilesDoubleWide = { - { BattleSide::ATTACKER, precalculateNeighbouringTilesDblWide(BattleSide::ATTACKER) }, - { BattleSide::DEFENDER, precalculateNeighbouringTilesDblWide(BattleSide::DEFENDER) } + { BattleSide::ATTACKER, precalculateNeighbouringTilesDoubleWide(BattleSide::ATTACKER) }, + { BattleSide::DEFENDER, precalculateNeighbouringTilesDoubleWide(BattleSide::DEFENDER) } }; VCMI_LIB_NAMESPACE_END \ No newline at end of file diff --git a/lib/battle/BattleHexArray.h b/lib/battle/BattleHexArray.h index b754b3420..1cc19f601 100644 --- a/lib/battle/BattleHexArray.h +++ b/lib/battle/BattleHexArray.h @@ -60,7 +60,7 @@ public: { if(tile.isAvailable() && !contains(tile)) { - presenceFlags[tile] = 1; + presenceFlags[tile.toInt()] = true; internalStorage.emplace_back(tile); } } @@ -70,7 +70,7 @@ public: if(contains(hex)) return; - presenceFlags[hex] = 1; + presenceFlags[hex.toInt()] = true; internalStorage.emplace_back(hex); } @@ -87,7 +87,7 @@ public: if(contains(hex)) return; - presenceFlags[hex] = 1; + presenceFlags[hex.toInt()] = true; internalStorage[index] = hex; } @@ -96,7 +96,7 @@ public: if(contains(hex)) return pos; - presenceFlags[hex] = 1; + presenceFlags[hex.toInt()] = true; return internalStorage.insert(pos, hex); } @@ -122,7 +122,7 @@ public: void erase(iterator first, iterator last) noexcept; inline void pop_back() noexcept { - presenceFlags[internalStorage.back()] = 0; + presenceFlags[internalStorage.back().toInt()] = false; internalStorage.pop_back(); } @@ -158,33 +158,33 @@ public: } /// get (precomputed) all possible surrounding tiles - static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex) + static const BattleHexArray & getAllNeighbouringTiles(BattleHex hex) noexcept { assert(hex.isValid()); - return allNeighbouringTiles[hex]; + return allNeighbouringTiles[hex.toInt()]; } /// get (precomputed) only valid and available surrounding tiles - static const BattleHexArray & getNeighbouringTiles(BattleHex hex) + static const BattleHexArray & getNeighbouringTiles(BattleHex hex) noexcept { assert(hex.isValid()); - return neighbouringTiles[hex]; + return neighbouringTiles[hex.toInt()]; } /// get (precomputed) only valid and available surrounding tiles for double wide creatures - static const BattleHexArray & getNeighbouringTilesDblWide(BattleHex hex, BattleSide side) + static const BattleHexArray & getNeighbouringTilesDoubleWide(BattleHex hex, BattleSide side) noexcept { assert(hex.isValid() && (side == BattleSide::ATTACKER || side == BattleSide::DEFENDER)); - return neighbouringTilesDblWide.at(side)[hex]; + return neighbouringTilesDoubleWide.at(side)[hex.toInt()]; } [[nodiscard]] inline bool contains(BattleHex hex) const noexcept { if(hex.isValid()) - return presenceFlags[hex]; + return presenceFlags[hex.toInt()]; /* if(!isTower(hex)) logGlobal->warn("BattleHexArray::contains( %d ) - invalid BattleHex!", hex); @@ -198,10 +198,10 @@ public: void serialize(Serializer & s) { s & internalStorage; - if(!internalStorage.empty() && presenceFlags[internalStorage.front()] == 0) + if(!s.saving) { for(auto hex : internalStorage) - presenceFlags[hex] = 1; + presenceFlags[hex.toInt()] = true; } } @@ -307,11 +307,11 @@ private: static const ArrayOfBattleHexArrays neighbouringTiles; static const ArrayOfBattleHexArrays allNeighbouringTiles; - static const std::map neighbouringTilesDblWide; + static const std::map neighbouringTilesDoubleWide; static ArrayOfBattleHexArrays precalculateNeighbouringTiles(); static ArrayOfBattleHexArrays precalculateAllNeighbouringTiles(); - static ArrayOfBattleHexArrays precalculateNeighbouringTilesDblWide(BattleSide side); + static ArrayOfBattleHexArrays precalculateNeighbouringTilesDoubleWide(BattleSide side); }; VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleInfo.cpp b/lib/battle/BattleInfo.cpp index c28165df5..95670694a 100644 --- a/lib/battle/BattleInfo.cpp +++ b/lib/battle/BattleInfo.cpp @@ -46,7 +46,7 @@ CStack * BattleInfo::generateNewStack(uint32_t id, const CStackInstance & base, assert(!owner.isValidPlayer() || (base.armyObj && base.armyObj->tempOwner == owner)); auto * ret = new CStack(&base, owner, id, side, slot); - ret->initialPosition = getAvailableHex(base.getCreatureID(), side, position); //TODO: what if no free tile on battlefield was found? + ret->initialPosition = getAvailableHex(base.getCreatureID(), side, position.toInt()); //TODO: what if no free tile on battlefield was found? stacks.push_back(ret); return ret; } @@ -264,7 +264,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const for(BattleHex blocked : obi.getBlocked(pos)) { - if(tileAccessibility[blocked] == EAccessibility::UNAVAILABLE) //for ship-to-ship battlefield - exclude hardcoded unavailable tiles + if(tileAccessibility[blocked.toInt()] == EAccessibility::UNAVAILABLE) //for ship-to-ship battlefield - exclude hardcoded unavailable tiles return false; if(blockedTiles.contains(blocked)) return false; diff --git a/lib/battle/CBattleInfoCallback.cpp b/lib/battle/CBattleInfoCallback.cpp index 637e76396..32a3ec0e5 100644 --- a/lib/battle/CBattleInfoCallback.cpp +++ b/lib/battle/CBattleInfoCallback.cpp @@ -43,19 +43,15 @@ static BattleHex lineToWallHex(int line) //returns hex with wall in given line ( static bool sameSideOfWall(BattleHex pos1, BattleHex pos2) { - const int wallInStackLine = lineToWallHex(pos1.getY()); - const int wallInDestLine = lineToWallHex(pos2.getY()); - - const bool stackLeft = pos1 < wallInStackLine; - const bool destLeft = pos2 < wallInDestLine; + const bool stackLeft = pos1 < lineToWallHex(pos1.getY()); + const bool destLeft = pos2 < lineToWallHex(pos2.getY()); return stackLeft == destLeft; } static bool isInsideWalls(BattleHex pos) { - const int wallInStackLine = lineToWallHex(pos.getY()); - return wallInStackLine < pos; + return lineToWallHex(pos.getY()) < pos; } // parts of wall @@ -79,9 +75,10 @@ static const std::pair wallParts[] = static EWallPart hexToWallPart(BattleHex hex) { + si16 hexValue = hex.toInt(); for(const auto & elem : wallParts) { - if(elem.first == hex) + if(elem.first == hexValue) return elem.second; } @@ -151,7 +148,7 @@ std::pair< BattleHexArray, int > CBattleInfoCallback::getPath(BattleHex start, B { auto reachability = getReachability(stack); - if(reachability.predecessors[dest] == -1) //cannot reach destination + if(reachability.predecessors[dest.toInt()] == -1) //cannot reach destination { return std::make_pair(BattleHexArray(), 0); } @@ -162,10 +159,10 @@ std::pair< BattleHexArray, int > CBattleInfoCallback::getPath(BattleHex start, B while(curElem != start) { path.insert(curElem); - curElem = reachability.predecessors[curElem]; + curElem = reachability.predecessors[curElem.toInt()]; } - return std::make_pair(path, reachability.distances[dest]); + return std::make_pair(path, reachability.distances[dest.toInt()]); } bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const @@ -176,7 +173,7 @@ bool CBattleInfoCallback::battleIsInsideWalls(BattleHex from) const bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const { if (!from.isAvailable() || !dest.isAvailable()) - throw std::runtime_error("Invalid hex (" + std::to_string(from) + " and " + std::to_string(dest) + ") received in battleHasPenaltyOnLine!" ); + throw std::runtime_error("Invalid hex (" + std::to_string(from.toInt()) + " and " + std::to_string(dest.toInt()) + ") received in battleHasPenaltyOnLine!" ); auto isTileBlocked = [&](BattleHex tile) { @@ -225,7 +222,7 @@ bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, auto obstacles = battleGetAllObstaclesOnPos(hex, false); - if(hex != BattleHex::GATE_BRIDGE || (battleIsGatePassable())) + if(hex.toInt() != BattleHex::GATE_BRIDGE || (battleIsGatePassable())) for(const auto & obst : obstacles) if(obst->obstacleType == CObstacleInstance::MOAT) pathHasMoat |= true; @@ -786,7 +783,7 @@ DamageEstimation CBattleInfoCallback::battleEstimateDamage(const battle::Unit * { RETURN_IF_NOT_BATTLE({}); auto reachability = battleGetDistances(attacker, attacker->getPosition()); - int movementRange = attackerPosition.isValid() ? reachability[attackerPosition] : 0; + int movementRange = attackerPosition.isValid() ? reachability[attackerPosition.toInt()] : 0; return battleEstimateDamage(attacker, defender, movementRange, retaliationDmg); } @@ -955,8 +952,8 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const //removing accessibility for side columns of hexes for(int y = 0; y < GameConstants::BFIELD_HEIGHT; y++) { - ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y)] = EAccessibility::SIDE_COLUMN; - ret[BattleHex(0, y)] = EAccessibility::SIDE_COLUMN; + ret[BattleHex(GameConstants::BFIELD_WIDTH - 1, y).toInt()] = EAccessibility::SIDE_COLUMN; + ret[BattleHex(0, y).toInt()] = EAccessibility::SIDE_COLUMN; } //special battlefields with logically unavailable tiles @@ -965,7 +962,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const if(bFieldType != BattleField::NONE) { for(auto hex : bFieldType.getInfo()->impassableHexes) - ret[hex] = EAccessibility::UNAVAILABLE; + ret[hex.toInt()] = EAccessibility::UNAVAILABLE; } //gate -> should be before stacks @@ -990,14 +987,14 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const { for(auto hex : unit->getHexes()) if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns - ret[hex] = EAccessibility::ALIVE_STACK; + ret[hex.toInt()] = EAccessibility::ALIVE_STACK; } //obstacles for(const auto &obst : battleGetAllObstacles()) { for(auto hex : obst->getBlockedTiles()) - ret[hex] = EAccessibility::OBSTACLE; + ret[hex.toInt()] = EAccessibility::OBSTACLE; } //walls @@ -1020,7 +1017,7 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const for(const auto & elem : lockedIfNotDestroyed) { if(battleGetWallState(elem.first) != EWallState::DESTROYED) - ret[elem.second] = EAccessibility::DESTRUCTIBLE_WALL; + ret[elem.second.toInt()] = EAccessibility::DESTRUCTIBLE_WALL; } } @@ -1029,15 +1026,15 @@ AccessibilityInfo CBattleInfoCallback::getAccessibility() const AccessibilityInfo CBattleInfoCallback::getAccessibility(const battle::Unit * stack) const { - return getAccessibility(& battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide())); + return getAccessibility(battle::Unit::getHexes(stack->getPosition(), stack->doubleWide(), stack->unitSide())); } -AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray * accessibleHexes) const +AccessibilityInfo CBattleInfoCallback::getAccessibility(const BattleHexArray & accessibleHexes) const { auto ret = getAccessibility(); - for(auto hex : *accessibleHexes) + for(auto hex : accessibleHexes) if(hex.isValid()) - ret[hex] = EAccessibility::ACCESSIBLE; + ret[hex.toInt()] = EAccessibility::ACCESSIBLE; return ret; } @@ -1062,7 +1059,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib //first element hexq.push(params.startPosition); - ret.distances[params.startPosition] = 0; + ret.distances[params.startPosition.toInt()] = 0; std::array accessibleCache{}; for(int hex = 0; hex < GameConstants::BFIELD_SIZE; hex++) @@ -1077,7 +1074,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib if(isInObstacle(curHex, obstacles, checkParams)) continue; - const int costToNeighbour = ret.distances.at(curHex) + 1; + const int costToNeighbour = ret.distances.at(curHex.toInt()) + 1; for(BattleHex neighbour : curHex.getNeighbouringTiles()) { @@ -1085,7 +1082,7 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib if(params.bypassEnemyStacks) { - auto enemyToBypass = params.destructibleEnemyTurns.at(neighbour); + auto enemyToBypass = params.destructibleEnemyTurns.at(neighbour.toInt()); if(enemyToBypass >= 0) { @@ -1093,13 +1090,13 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo & accessib } } - const int costFoundSoFar = ret.distances[neighbour]; + const int costFoundSoFar = ret.distances[neighbour.toInt()]; - if(accessibleCache[neighbour] && costToNeighbour + additionalCost < costFoundSoFar) + if(accessibleCache[neighbour.toInt()] && costToNeighbour + additionalCost < costFoundSoFar) { hexq.push(neighbour); - ret.distances[neighbour] = costToNeighbour + additionalCost; - ret.predecessors[neighbour] = curHex; + ret.distances[neighbour.toInt()] = costToNeighbour + additionalCost; + ret.predecessors[neighbour.toInt()] = curHex; } } } @@ -1181,7 +1178,7 @@ std::pair CBattleInfoCallback::getNearestStack( for(BattleHex hex : avHexes) if(CStack::isMeleeAttackPossible(closest, st, hex)) { - DistStack hlp = {reachability.distances[hex], hex, st}; + DistStack hlp = {reachability.distances[hex.toInt()], hex, st}; stackPairs.push_back(hlp); } } @@ -1272,9 +1269,12 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa return getFlyingReachability(params); else { - auto accessibility = getAccessibility(params.knownAccessible); + auto accessibility = getAccessibility(* params.knownAccessible); - accessibility.destructibleEnemyTurns = & params.destructibleEnemyTurns; + accessibility.destructibleEnemyTurns = std::shared_ptr( + & params.destructibleEnemyTurns, + [](const TBattlefieldTurnsArray *) { } + ); return makeBFS(accessibility, params); } @@ -1283,7 +1283,7 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters & params) const { ReachabilityInfo ret; - ret.accessibility = getAccessibility(params.knownAccessible); + ret.accessibility = getAccessibility(* params.knownAccessible); for(int i = 0; i < GameConstants::BFIELD_SIZE; i++) { @@ -1326,9 +1326,9 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes( AttackableTiles at; RETURN_IF_NOT_BATTLE(at); - BattleHex attackOriginHex = (attackerPos != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position + BattleHex attackOriginHex = (attackerPos.toInt() != BattleHex::INVALID) ? attackerPos : attacker->getPosition(); //real or hypothetical (cursor) position - defenderPos = (defenderPos != BattleHex::INVALID) ? defenderPos : defender->getPosition(); //real or hypothetical (cursor) position + defenderPos = (defenderPos.toInt() != BattleHex::INVALID) ? defenderPos : defender->getPosition(); //real or hypothetical (cursor) position bool reverse = isToReverse(attacker, defender, attackerPos, defenderPos); if(reverse && attacker->doubleWide()) diff --git a/lib/battle/CBattleInfoCallback.h b/lib/battle/CBattleInfoCallback.h index 3d877a5e1..e7c11d5da 100644 --- a/lib/battle/CBattleInfoCallback.h +++ b/lib/battle/CBattleInfoCallback.h @@ -162,7 +162,7 @@ public: ReachabilityInfo getReachability(const ReachabilityInfo::Parameters & params) const; AccessibilityInfo getAccessibility() const; AccessibilityInfo getAccessibility(const battle::Unit * stack) const; //Hexes occupied by stack will be marked as accessible. - AccessibilityInfo getAccessibility(const BattleHexArray * accessibleHexes) const; //given hexes will be marked as accessible + AccessibilityInfo getAccessibility(const BattleHexArray & accessibleHexes) const; //given hexes will be marked as accessible std::pair getNearestStack(const battle::Unit * closest) const; BattleHex getAvailableHex(const CreatureID & creID, BattleSide side, int initialPos = -1) const; //find place for adding new stack diff --git a/lib/battle/CObstacleInstance.cpp b/lib/battle/CObstacleInstance.cpp index c0d45def2..5be15e744 100644 --- a/lib/battle/CObstacleInstance.cpp +++ b/lib/battle/CObstacleInstance.cpp @@ -113,7 +113,9 @@ void CObstacleInstance::serializeJson(JsonSerializeFormat & handler) animationYOffset -= 42; //We need only a subset of obstacle info for correct render - handler.serializeInt("position", pos); + si16 posValue = pos.toInt(); + handler.serializeInt("position", posValue); + pos = posValue; handler.serializeInt("animationYOffset", animationYOffset); handler.serializeBool("hidden", hidden); handler.serializeBool("needAnimationOffsetFix", needAnimationOffsetFix); @@ -188,7 +190,9 @@ void SpellCreatedObstacle::fromInfo(const ObstacleChanges & info) void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler) { handler.serializeInt("spell", ID); - handler.serializeInt("position", pos); + si16 posValue = pos.toInt(); + handler.serializeInt("position", posValue); + pos = posValue; handler.serializeInt("turnsRemaining", turnsRemaining); handler.serializeInt("casterSpellPower", casterSpellPower); @@ -215,9 +219,9 @@ void SpellCreatedObstacle::serializeJson(JsonSerializeFormat & handler) JsonArraySerializer customSizeJson = handler.enterArray("customSize"); customSizeJson.syncSize(customSize, JsonNode::JsonType::DATA_INTEGER); - BattleHex hex; for(size_t index = 0; index < customSizeJson.size(); index++) { + si16 hex = customSize.at(index).toInt(); customSizeJson.serializeInt(index, hex); customSize.set(index, hex); } diff --git a/lib/battle/CUnitState.cpp b/lib/battle/CUnitState.cpp index 8c40875e7..4291ecc14 100644 --- a/lib/battle/CUnitState.cpp +++ b/lib/battle/CUnitState.cpp @@ -798,7 +798,9 @@ void CUnitState::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("cloneID", cloneID); - handler.serializeInt("position", position); + si16 posValue = position.toInt(); + handler.serializeInt("position", posValue); + position = posValue; } void CUnitState::localInit(const IUnitEnvironment * env_) diff --git a/lib/battle/DamageCalculator.cpp b/lib/battle/DamageCalculator.cpp index b6030e87e..419eafb26 100644 --- a/lib/battle/DamageCalculator.cpp +++ b/lib/battle/DamageCalculator.cpp @@ -45,7 +45,7 @@ DamageRange DamageCalculator::getBaseDamageSingle() const const auto * town = callback.battleGetDefendedTown(); assert(town); - switch(info.attacker->getPosition()) + switch(info.attacker->getPosition().toInt()) { case BattleHex::CASTLE_CENTRAL_TOWER: return town->getKeepDamageRange(); diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index b16496bce..7a7bae3ea 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -33,7 +33,7 @@ ReachabilityInfo::ReachabilityInfo() bool ReachabilityInfo::isReachable(BattleHex hex) const { - return distances[hex] < INFINITE_DIST; + return distances[hex.toInt()] < INFINITE_DIST; } uint32_t ReachabilityInfo::distToNearestNeighbour( @@ -46,9 +46,9 @@ uint32_t ReachabilityInfo::distToNearestNeighbour( { for(auto & n : targetHex.getNeighbouringTiles()) { - if(distances[n] < ret) + if(distances[n.toInt()] < ret) { - ret = distances[n]; + ret = distances[n.toInt()]; if(chosenHex) *chosenHex = n; } diff --git a/lib/battle/ReachabilityInfo.h b/lib/battle/ReachabilityInfo.h index 29687df66..b21c99246 100644 --- a/lib/battle/ReachabilityInfo.h +++ b/lib/battle/ReachabilityInfo.h @@ -33,7 +33,7 @@ struct DLL_LINKAGE ReachabilityInfo bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward const BattleHexArray * knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself) - std::array destructibleEnemyTurns; // how many turns it is needed to kill enemy on specific hex (index <=> hex) + TBattlefieldTurnsArray destructibleEnemyTurns; // how many turns it is needed to kill enemy on specific hex (index <=> hex) BattleHex startPosition; //assumed position of stack BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side diff --git a/lib/battle/Unit.cpp b/lib/battle/Unit.cpp index fdb97d4d4..57a36eb6a 100644 --- a/lib/battle/Unit.cpp +++ b/lib/battle/Unit.cpp @@ -53,7 +53,7 @@ const IBonusBearer* Unit::getBonusBearer() const const BattleHexArray & Unit::getSurroundingHexes(BattleHex assumedPosition) const { - BattleHex hex = (assumedPosition != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position + BattleHex hex = (assumedPosition.toInt() != BattleHex::INVALID) ? assumedPosition : getPosition(); //use hypothetical position return getSurroundingHexes(hex, doubleWide(), unitSide()); } @@ -63,7 +63,7 @@ const BattleHexArray & Unit::getSurroundingHexes(BattleHex position, bool twoHex if(!twoHex) return position.getNeighbouringTiles(); - return position.getNeighbouringTilesDblWide(side); + return position.getNeighbouringTilesDoubleWide(side); } BattleHexArray Unit::getAttackableHexes(const Unit * attacker) const @@ -112,8 +112,8 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleS static BattleHexArray::ArrayOfBattleHexArrays precomputed[4]; int index = side == BattleSide::ATTACKER ? 0 : 2; - if(!precomputed[index + twoHex][assumedPos].empty()) - return precomputed[index + twoHex][assumedPos]; + if(!precomputed[index + twoHex][assumedPos.toInt()].empty()) + return precomputed[index + twoHex][assumedPos.toInt()]; // first run, compute @@ -123,9 +123,9 @@ const BattleHexArray & Unit::getHexes(BattleHex assumedPos, bool twoHex, BattleS if(twoHex) hexes.insert(occupiedHex(assumedPos, twoHex, side)); - precomputed[index + twoHex][assumedPos] = std::move(hexes); + precomputed[index + twoHex][assumedPos.toInt()] = std::move(hexes); - return precomputed[index + twoHex][assumedPos]; + return precomputed[index + twoHex][assumedPos.toInt()]; } BattleHex Unit::occupiedHex() const @@ -143,9 +143,9 @@ BattleHex Unit::occupiedHex(BattleHex assumedPos, bool twoHex, BattleSide side) if(twoHex) { if(side == BattleSide::ATTACKER) - return assumedPos - 1; + return assumedPos.toInt() - 1; else - return assumedPos + 1; + return assumedPos.toInt() + 1; } else { @@ -201,7 +201,9 @@ void UnitInfo::serializeJson(JsonSerializeFormat & handler) handler.serializeInt("count", count); handler.serializeId("type", type, CreatureID(CreatureID::NONE)); handler.serializeInt("side", side); - handler.serializeInt("position", position); + si16 positionValue = position.toInt(); + handler.serializeInt("position", positionValue); + position = positionValue; handler.serializeBool("summoned", summoned); } diff --git a/lib/battle/Unit.h b/lib/battle/Unit.h index 8973cca73..9708b124f 100644 --- a/lib/battle/Unit.h +++ b/lib/battle/Unit.h @@ -84,11 +84,14 @@ public: bool isTurret() const; virtual bool isValidTarget(bool allowDead = false) const = 0; //non-turret non-ghost stacks (can be attacked or be object of magic effect) + virtual bool isHypnotized() const = 0; + virtual bool isClone() const = 0; virtual bool hasClone() const = 0; virtual bool canCast() const = 0; virtual bool isCaster() const = 0; + virtual bool canShootBlocked() const = 0; virtual bool canShoot() const = 0; virtual bool isShooter() const = 0; @@ -112,8 +115,6 @@ public: virtual BattleHex getPosition() const = 0; virtual void setPosition(BattleHex hex) = 0; - virtual int32_t getInitiative(int turn = 0) const = 0; - virtual bool canMove(int turn = 0) const = 0; //if stack can move virtual bool defended(int turn = 0) const = 0; virtual bool moved(int turn = 0) const = 0; //if stack was already moved this turn diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index 993782c47..0334f0127 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -238,7 +238,7 @@ JsonNode UnitOnHexLimiter::toJsonNode() const root["type"].String() = "UNIT_ON_HEXES"; for(auto hex : applicableHexes) - root["parameters"].Vector().emplace_back(hex); + root["parameters"].Vector().emplace_back(hex.toInt()); return root; } diff --git a/lib/spells/BattleSpellMechanics.cpp b/lib/spells/BattleSpellMechanics.cpp index 97ec2bbe0..0b02d5002 100644 --- a/lib/spells/BattleSpellMechanics.cpp +++ b/lib/spells/BattleSpellMechanics.cpp @@ -517,7 +517,7 @@ BattleHexArray BattleSpellMechanics::spellRangeInHexes(BattleHex centralHex) con for(auto & elem : rng) { - std::set curLayer = getInRange(centralHex, elem, elem); + std::set curLayer = getInRange(centralHex.toInt(), elem, elem); //adding obtained hexes for(const auto & curLayer_it : curLayer) ret.insert(curLayer_it); diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 09b3aadae..f357f2c9b 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -754,7 +754,14 @@ std::vector CSpellHandler::spellRangeInHexes(std::string input) const } } - return std::vector(ret.begin(), ret.end()); + std::vector result; + result.reserve(ret.size()); + + std::transform(ret.begin(), ret.end(), std::back_inserter(result), + [](BattleHex hex) { return hex.toInt(); } + ); + + return result; } std::shared_ptr CSpellHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) diff --git a/lib/spells/effects/Catapult.cpp b/lib/spells/effects/Catapult.cpp index 28ec7998e..763ad3045 100644 --- a/lib/spells/effects/Catapult.cpp +++ b/lib/spells/effects/Catapult.cpp @@ -95,7 +95,7 @@ void Catapult::applyMassive(ServerCallback * server, const Mechanics * m) const CatapultAttack::AttackInfo newInfo; newInfo.damageDealt = getRandomDamage(server); newInfo.attackedPart = target; - newInfo.destinationTile = m->battle()->wallPartToBattleHex(target); + newInfo.destinationTile = m->battle()->wallPartToBattleHex(target).toInt(); ca.attackedParts.push_back(newInfo); attackInfo = ca.attackedParts.end() - 1; } @@ -137,7 +137,7 @@ void Catapult::applyTargeted(ServerCallback * server, const Mechanics * m, const CatapultAttack::AttackInfo attack; attack.attackedPart = actualTarget; - attack.destinationTile = m->battle()->wallPartToBattleHex(actualTarget); + attack.destinationTile = m->battle()->wallPartToBattleHex(actualTarget).toInt(); attack.damageDealt = getRandomDamage(server); CatapultAttack ca; //package for clients diff --git a/lib/spells/effects/Clone.cpp b/lib/spells/effects/Clone.cpp index 03f35ff9d..389b5b65f 100644 --- a/lib/spells/effects/Clone.cpp +++ b/lib/spells/effects/Clone.cpp @@ -43,7 +43,7 @@ void Clone::apply(ServerCallback * server, const Mechanics * m, const EffectTarg if(clonedStack->getCount() < 1) continue; - auto hex = m->battle()->getAvailableHex(clonedStack->creatureId(), m->casterSide, clonedStack->getPosition()); + auto hex = m->battle()->getAvailableHex(clonedStack->creatureId(), m->casterSide, clonedStack->getPosition().toInt()); if(!hex.isValid()) { diff --git a/lib/spells/effects/DemonSummon.cpp b/lib/spells/effects/DemonSummon.cpp index b86a87d1a..853a80381 100644 --- a/lib/spells/effects/DemonSummon.cpp +++ b/lib/spells/effects/DemonSummon.cpp @@ -65,7 +65,7 @@ void DemonSummon::apply(ServerCallback * server, const Mechanics * m, const Effe continue; } - auto hex = m->battle()->getAvailableHex(targetStack->creatureId(), m->casterSide, targetStack->getPosition()); + auto hex = m->battle()->getAvailableHex(targetStack->creatureId(), m->casterSide, targetStack->getPosition().toInt()); if(!hex.isValid()) { diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index e8568df1c..7cd1c3ebe 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -43,9 +43,9 @@ static void serializeMoatHexes(JsonSerializeFormat & handler, const std::string JsonArraySerializer inner = outer.enterArray(outerIndex); inner.syncSize(moatHexes.at(outerIndex), JsonNode::JsonType::DATA_INTEGER); - BattleHex hex; for(size_t innerIndex = 0; innerIndex < inner.size(); innerIndex++) { + si16 hex = moatHexes.at(outerIndex).at(innerIndex).toInt(); inner.serializeInt(innerIndex, hex); moatHexes.at(outerIndex).set(innerIndex, hex); } diff --git a/lib/spells/effects/UnitEffect.cpp b/lib/spells/effects/UnitEffect.cpp index 6704a5125..8a3a543d1 100644 --- a/lib/spells/effects/UnitEffect.cpp +++ b/lib/spells/effects/UnitEffect.cpp @@ -223,7 +223,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe effectTarget.emplace_back(); for(auto hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide())) - possibleHexes.erase(hex); + possibleHexes.erase(hex.toInt()); if(possibleHexes.empty()) break; diff --git a/scripting/lua/LuaSpellEffect.cpp b/scripting/lua/LuaSpellEffect.cpp index 89e9e362f..e6717f104 100644 --- a/scripting/lua/LuaSpellEffect.cpp +++ b/scripting/lua/LuaSpellEffect.cpp @@ -98,7 +98,7 @@ bool LuaSpellEffect::applicable(Problem & problem, const Mechanics * m, const Ef for(const auto & dest : target) { JsonNode targetData; - targetData.Vector().emplace_back(static_cast(dest.hexValue)); + targetData.Vector().emplace_back(dest.hexValue.toInt()); if(dest.unitValue) targetData.Vector().emplace_back(dest.unitValue->unitId()); @@ -141,7 +141,7 @@ void LuaSpellEffect::apply(ServerCallback * server, const Mechanics * m, const E for(const auto & dest : target) { JsonNode targetData; - targetData.Vector().emplace_back(static_cast(dest.hexValue)); + targetData.Vector().emplace_back(dest.hexValue.toInt()); if(dest.unitValue) targetData.Vector().emplace_back(dest.unitValue->unitId()); diff --git a/test/scripting/LuaSpellEffectAPITest.cpp b/test/scripting/LuaSpellEffectAPITest.cpp index dad0236e0..f577cb3b8 100644 --- a/test/scripting/LuaSpellEffectAPITest.cpp +++ b/test/scripting/LuaSpellEffectAPITest.cpp @@ -84,7 +84,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplicableOnLeftSideOfField) BattleHex hex(2,2); JsonNode first; - first.Vector().emplace_back(static_cast(hex)); + first.Vector().emplace_back(hex.toInt()); first.Vector().emplace_back(); JsonNode targets; @@ -113,7 +113,7 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_NotApplicableOnRightSideOfField) BattleHex hex(11,2); JsonNode first; - first.Vector().emplace_back(static_cast(hex)); + first.Vector().emplace_back(hex.toInt()); first.Vector().emplace_back(-1); JsonNode targets; @@ -138,13 +138,13 @@ TEST_F(LuaSpellEffectAPITest, DISABLED_ApplyMoveUnit) BattleHex hex1(11,2); JsonNode unit; - unit.Vector().emplace_back(static_cast(hex1)); + unit.Vector().emplace_back(hex1.toInt()); unit.Vector().emplace_back(42); BattleHex hex2(5,4); JsonNode destination; - destination.Vector().emplace_back(static_cast(hex2)); + destination.Vector().emplace_back(hex2.toInt()); destination.Vector().emplace_back(-1); JsonNode targets; diff --git a/test/scripting/LuaSpellEffectTest.cpp b/test/scripting/LuaSpellEffectTest.cpp index 0fac27fdf..16fcc2053 100644 --- a/test/scripting/LuaSpellEffectTest.cpp +++ b/test/scripting/LuaSpellEffectTest.cpp @@ -154,11 +154,11 @@ TEST_F(LuaSpellEffectTest, ApplicableTargetRedirected) JsonNode first; - first.Vector().emplace_back(static_cast(hex1)); + first.Vector().emplace_back(hex1.toInt()); first.Vector().emplace_back(id1); JsonNode second; - second.Vector().emplace_back(static_cast(hex2)); + second.Vector().emplace_back(hex2.toInt()); second.Vector().emplace_back(-1); JsonNode targets; @@ -193,7 +193,7 @@ TEST_F(LuaSpellEffectTest, ApplyRedirected) subject->apply(&serverMock, &mechanicsMock, target); JsonNode first; - first.Vector().emplace_back(static_cast(hex1)); + first.Vector().emplace_back(hex1.toInt()); first.Vector().emplace_back(id1); JsonNode targets;