From a99274d72eff6926e79a447a922ba7b186a381d2 Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Sat, 28 Sep 2024 20:50:26 +0200 Subject: [PATCH] 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); +};