From 8c3a4175276fdbd575b86d009253d083ef879dac Mon Sep 17 00:00:00 2001 From: MichalZr6 Date: Tue, 12 Nov 2024 07:11:18 +0100 Subject: [PATCH] Cleanup --- AI/BattleAI/BattleAI.h | 2 +- AI/Nullkiller/Helpers/ExplorationHelper.h | 98 +- AI/Nullkiller/Pathfinding/AINodeStorage.cpp | 2 +- .../Pathfinding/ObjectGraphCalculator.cpp | 28 +- .../Pathfinding/ObjectGraphCalculator.h | 112 +- AI/StupidAI/StupidAI.h | 112 +- AI/VCAI/Goals/Explore.cpp | 896 +++---- AI/VCAI/Goals/Explore.h | 132 +- client/battle/BattleFieldController.cpp | 3 + client/battle/BattleFieldController.h | 282 +-- client/battle/BattleInterface.h | 464 ++-- client/battle/BattleStacksController.h | 298 +-- include/vstd/RNG.h | 208 +- lib/BattleFieldHandler.cpp | 202 +- lib/BattleFieldHandler.h | 160 +- lib/ObstacleHandler.cpp | 258 +-- lib/ObstacleHandler.h | 158 +- lib/battle/BattleHex.cpp | 276 +-- lib/battle/BattleHex.h | 254 +- lib/battle/Destination.h | 86 +- lib/battle/IBattleInfoCallback.h | 178 +- lib/battle/ReachabilityInfo.cpp | 178 +- lib/bonuses/Limiters.cpp | 6 +- lib/pathfinder/CGPathNode.h | 486 ++-- lib/rmg/CZonePlacer.cpp | 2054 ++++++++--------- lib/rmg/RmgPath.cpp | 1 + lib/rmg/modificators/ObstaclePlacer.cpp | 2 +- lib/serializer/BinaryDeserializer.h | 14 - lib/serializer/BinarySerializer.h | 1 - lib/spells/BattleSpellMechanics.h | 172 +- lib/spells/ISpellMechanics.h | 736 +++--- lib/spells/effects/Effect.h | 166 +- lib/spells/effects/LocationEffect.cpp | 104 +- lib/spells/effects/LocationEffect.h | 82 +- lib/spells/effects/Moat.h | 86 +- lib/spells/effects/Obstacle.h | 156 +- lib/spells/effects/Summon.h | 110 +- lib/spells/effects/UnitEffect.h | 122 +- server/battles/BattleFlowProcessor.h | 124 +- 39 files changed, 4399 insertions(+), 4410 deletions(-) diff --git a/AI/BattleAI/BattleAI.h b/AI/BattleAI/BattleAI.h index 18df749f0..4889afe42 100644 --- a/AI/BattleAI/BattleAI.h +++ b/AI/BattleAI/BattleAI.h @@ -88,7 +88,7 @@ public: //void battleResultsApplied() override; //called when all effects of last battle are applied //void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; //void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn - //void battleStackMoved(const CStack * stack, std::vector dest, int distance) override; + //void battleStackMoved(const CStack * stack, BattleHexArray dest, int distance) override; //void battleSpellCast(const BattleSpellCast *sc) override; //void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks //void battleTriggerEffect(const BattleTriggerEffect & bte) override; diff --git a/AI/Nullkiller/Helpers/ExplorationHelper.h b/AI/Nullkiller/Helpers/ExplorationHelper.h index 0d58f341d..cd4427de1 100644 --- a/AI/Nullkiller/Helpers/ExplorationHelper.h +++ b/AI/Nullkiller/Helpers/ExplorationHelper.h @@ -1,49 +1,49 @@ -/* -* ExplorationHelper.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#pragma once - -#include "../AIUtility.h" - -#include "../../../lib/GameConstants.h" -#include "../../../lib/VCMI_Lib.h" -#include "../Goals/AbstractGoal.h" - -namespace NKAI -{ - -class ExplorationHelper -{ -private: - const CGHeroInstance * hero; - int sightRadius; - float bestValue; - Goals::TSubgoal bestGoal; - int3 bestTile; - int bestTilesDiscovered; - const Nullkiller * ai; - CCallback * cbp; - const TeamState * ts; - int3 ourPos; - bool allowDeadEndCancellation; - bool useCPathfinderAccessibility; - -public: - ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false); - Goals::TSubgoal makeComposition() const; - bool scanSector(int scanRadius); - bool scanMap(); - int howManyTilesWillBeDiscovered(const int3 & pos) const; - -private: - void scanTile(const int3 & tile); - bool hasReachableneighbour(const int3 & pos) const; -}; - -} +/* +* ExplorationHelper.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "../AIUtility.h" + +#include "../../../lib/GameConstants.h" +#include "../../../lib/VCMI_Lib.h" +#include "../Goals/AbstractGoal.h" + +namespace NKAI +{ + +class ExplorationHelper +{ +private: + const CGHeroInstance * hero; + int sightRadius; + float bestValue; + Goals::TSubgoal bestGoal; + int3 bestTile; + int bestTilesDiscovered; + const Nullkiller * ai; + CCallback * cbp; + const TeamState * ts; + int3 ourPos; + bool allowDeadEndCancellation; + bool useCPathfinderAccessibility; + +public: + ExplorationHelper(const CGHeroInstance * hero, const Nullkiller * ai, bool useCPathfinderAccessibility = false); + Goals::TSubgoal makeComposition() const; + bool scanSector(int scanRadius); + bool scanMap(); + int howManyTilesWillBeDiscovered(const int3 & pos) const; + +private: + void scanTile(const int3 & tile); + bool hasReachableNeighbor(const int3 & pos) const; +}; + +} diff --git a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp index de9ad89b3..a40fbd7d2 100644 --- a/AI/Nullkiller/Pathfinding/AINodeStorage.cpp +++ b/AI/Nullkiller/Pathfinding/AINodeStorage.cpp @@ -399,7 +399,7 @@ void AINodeStorage::calculateNeighbours( #if NKAI_PATHFINDER_TRACE_LEVEL >= 2 logAi->trace( - "Node %s added to neighbours of %s, layer %d", + "Node %s added to neighbors of %s, layer %d", neighbour.toString(), source.coord.toString(), static_cast(layer)); diff --git a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp index ddaff0e73..8905572c9 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp +++ b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.cpp @@ -56,9 +56,9 @@ void ObjectGraphCalculator::calculateConnections() removeExtraConnections(); } -float ObjectGraphCalculator::getneighbourConnectionsCost(const int3 & pos, std::vector & pathCache) +float ObjectGraphCalculator::getNeighborConnectionsCost(const int3 & pos, std::vector & pathCache) { - float neighbourCost = std::numeric_limits::max(); + float neighborCost = std::numeric_limits::max(); if(NKAI_GRAPH_TRACE_LEVEL >= 2) { @@ -68,24 +68,24 @@ float ObjectGraphCalculator::getneighbourConnectionsCost(const int3 & pos, std:: foreach_neighbour( ai->cb.get(), pos, - [this, &neighbourCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour) + [this, &neighborCost, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) { - ai->pathfinder->calculatePathInfo(pathCache, neighbour); + ai->pathfinder->calculatePathInfo(pathCache, neighbor); auto costTotal = this->getConnectionsCost(pathCache); - if(costTotal.connectionsCount > 2 && costTotal.avg < neighbourCost) + if(costTotal.connectionsCount > 2 && costTotal.avg < neighborCost) { - neighbourCost = costTotal.avg; + neighborCost = costTotal.avg; if(NKAI_GRAPH_TRACE_LEVEL >= 2) { - logAi->trace("Better node found at %s", neighbour.toString()); + logAi->trace("Better node found at %s", neighbor.toString()); } } }); - return neighbourCost; + return neighborCost; } void ObjectGraphCalculator::addMinimalDistanceJunctions() @@ -105,9 +105,9 @@ void ObjectGraphCalculator::addMinimalDistanceJunctions() if(currentCost.connectionsCount <= 2) return; - float neighbourCost = getneighbourConnectionsCost(pos, paths); + float neighborCost = getNeighborConnectionsCost(pos, paths); - if(currentCost.avg < neighbourCost) + if(currentCost.avg < neighborCost) { junctions.insert(pos); } @@ -137,17 +137,17 @@ void ObjectGraphCalculator::calculateConnections(const int3 & pos, std::vectorcb.get(), pos, - [this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbour) + [this, &pos, &pathCache](const CPlayerSpecificInfoCallback * cb, const int3 & neighbor) { - if(target->hasNodeAt(neighbour)) + if(target->hasNodeAt(neighbor)) { - ai->pathfinder->calculatePathInfo(pathCache, neighbour); + ai->pathfinder->calculatePathInfo(pathCache, neighbor); for(auto & path : pathCache) { if(pos == path.targetHero->visitablePos()) { - target->tryAddConnection(pos, neighbour, path.movementCost(), path.getTotalDanger()); + target->tryAddConnection(pos, neighbor, path.movementCost(), path.getTotalDanger()); } } } diff --git a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h index 67b03edff..812bd6985 100644 --- a/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h +++ b/AI/Nullkiller/Pathfinding/ObjectGraphCalculator.h @@ -1,56 +1,56 @@ -/* -* ObjectGraphCalculator.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ - -#pragma once - -#include "ObjectGraph.h" -#include "../AIUtility.h" - -namespace NKAI -{ - -struct ConnectionCostInfo -{ - float totalCost = 0; - float avg = 0; - int connectionsCount = 0; -}; - -class ObjectGraphCalculator -{ -private: - ObjectGraph * target; - const Nullkiller * ai; - std::mutex syncLock; - - std::map actors; - std::map actorObjectMap; - - std::vector> temporaryBoats; - std::vector> temporaryActorHeroes; - -public: - ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai); - void setGraphObjects(); - void calculateConnections(); - float getneighbourConnectionsCost(const int3 & pos, std::vector & pathCache); - void addMinimalDistanceJunctions(); - -private: - void updatePaths(); - void calculateConnections(const int3 & pos, std::vector & pathCache); - bool isExtraConnection(float direct, float side1, float side2) const; - void removeExtraConnections(); - void addObjectActor(const CGObjectInstance * obj); - void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false); - ConnectionCostInfo getConnectionsCost(std::vector & paths) const; -}; - -} +/* +* ObjectGraphCalculator.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ + +#pragma once + +#include "ObjectGraph.h" +#include "../AIUtility.h" + +namespace NKAI +{ + +struct ConnectionCostInfo +{ + float totalCost = 0; + float avg = 0; + int connectionsCount = 0; +}; + +class ObjectGraphCalculator +{ +private: + ObjectGraph * target; + const Nullkiller * ai; + std::mutex syncLock; + + std::map actors; + std::map actorObjectMap; + + std::vector> temporaryBoats; + std::vector> temporaryActorHeroes; + +public: + ObjectGraphCalculator(ObjectGraph * target, const Nullkiller * ai); + void setGraphObjects(); + void calculateConnections(); + float getNeighborConnectionsCost(const int3 & pos, std::vector & pathCache); + void addMinimalDistanceJunctions(); + +private: + void updatePaths(); + void calculateConnections(const int3 & pos, std::vector & pathCache); + bool isExtraConnection(float direct, float side1, float side2) const; + void removeExtraConnections(); + void addObjectActor(const CGObjectInstance * obj); + void addJunctionActor(const int3 & visitablePos, bool isVirtualBoat = false); + ConnectionCostInfo getConnectionsCost(std::vector & paths) const; +}; + +} diff --git a/AI/StupidAI/StupidAI.h b/AI/StupidAI/StupidAI.h index f1fe9171b..06a0e04e8 100644 --- a/AI/StupidAI/StupidAI.h +++ b/AI/StupidAI/StupidAI.h @@ -1,56 +1,56 @@ -/* - * StupidAI.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../../lib/battle/BattleHex.h" -#include "../../lib/battle/ReachabilityInfo.h" -#include "../../lib/CGameInterface.h" - -class EnemyInfo; - -class CStupidAI : public CBattleGameInterface -{ - BattleSide side; - std::shared_ptr cb; - std::shared_ptr env; - - bool wasWaitingForRealize; - bool wasUnlockingGs; - - void print(const std::string &text) const; -public: - CStupidAI(); - ~CStupidAI(); - - void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; - void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; - - void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero - void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero - void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack - void yourTacticPhase(const BattleID & battleID, int distance) override; - - void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack - void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) override; //called when stack receives damage (after battleAttack()) - void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; - //void battleResultsApplied() override; //called when all effects of last battle are applied - void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; - void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn - void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; - void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; - void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks - //void battleTriggerEffect(const BattleTriggerEffect & bte) override; - void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right - void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack - -private: - BattleAction goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const; -}; - +/* + * StupidAI.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../../lib/battle/BattleHex.h" +#include "../../lib/battle/ReachabilityInfo.h" +#include "../../lib/CGameInterface.h" + +class EnemyInfo; + +class CStupidAI : public CBattleGameInterface +{ + BattleSide side; + std::shared_ptr cb; + std::shared_ptr env; + + bool wasWaitingForRealize; + bool wasUnlockingGs; + + void print(const std::string &text) const; +public: + CStupidAI(); + ~CStupidAI(); + + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB) override; + void initBattleInterface(std::shared_ptr ENV, std::shared_ptr CB, AutocombatPreferences autocombatPreferences) override; + + void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero + void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero + void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack + void yourTacticPhase(const BattleID & battleID, int distance) override; + + void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack + void battleStacksAttacked(const BattleID & battleID, const std::vector & bsa, bool ranged) override; //called when stack receives damage (after battleAttack()) + void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; + //void battleResultsApplied() override; //called when all effects of last battle are applied + void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; + void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn + void battleStackMoved(const BattleID & battleID, const CStack * stack, BattleHexArray dest, int distance, bool teleport) override; + void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override; + void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks + //void battleTriggerEffect(const BattleTriggerEffect & bte) override; + void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right + void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack + +private: + BattleAction goTowards(const BattleID & battleID, const CStack * stack, BattleHexArray hexes) const; +}; + diff --git a/AI/VCAI/Goals/Explore.cpp b/AI/VCAI/Goals/Explore.cpp index 55527c056..294103598 100644 --- a/AI/VCAI/Goals/Explore.cpp +++ b/AI/VCAI/Goals/Explore.cpp @@ -1,448 +1,448 @@ -/* -* Explore.cpp, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#include "StdInc.h" -#include "Goals.h" -#include "../VCAI.h" -#include "../AIUtility.h" -#include "../AIhelper.h" -#include "../FuzzyHelper.h" -#include "../ResourceManager.h" -#include "../BuildingManager.h" -#include "../../../lib/constants/StringConstants.h" -#include "../../../lib/CPlayerState.h" - -using namespace Goals; - -namespace Goals -{ - struct ExplorationHelper - { - HeroPtr hero; - int sightRadius; - float bestValue; - TSubgoal bestGoal; - VCAI * aip; - CCallback * cbp; - const TeamState * ts; - int3 ourPos; - bool allowDeadEndCancellation; - bool allowGatherArmy; - - ExplorationHelper(HeroPtr h, bool gatherArmy) - { - cbp = cb; - aip = ai; - hero = h; - ts = cbp->getPlayerTeam(ai->playerID); - sightRadius = hero->getSightRadius(); - bestGoal = sptr(Goals::Invalid()); - bestValue = 0; - ourPos = h->visitablePos(); - allowDeadEndCancellation = true; - allowGatherArmy = gatherArmy; - } - - void scanSector(int scanRadius) - { - int3 tile = int3(0, 0, ourPos.z); - - const auto & slice = ts->fogOfWarMap[ourPos.z]; - - for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++) - { - for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++) - { - - if(cbp->isInTheMap(tile) && slice[tile.x][tile.y]) - { - scanTile(tile); - } - } - } - } - - void scanMap() - { - int3 mapSize = cbp->getMapSize(); - int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); - - std::vector from; - std::vector to; - - from.reserve(perimeter); - to.reserve(perimeter); - - foreach_tile_pos([&](const int3 & pos) - { - if(ts->fogOfWarMap[pos.z][pos.x][pos.y]) - { - bool hasInvisibleneighbour = false; - - foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) - { - if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) - { - hasInvisibleneighbour = true; - } - }); - - if(hasInvisibleneighbour) - from.push_back(pos); - } - }); - - logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name); - - for(const int3 & tile : from) - { - scanTile(tile); - } - - if(!bestGoal->invalid()) - { - return; - } - - allowDeadEndCancellation = false; - - for(int i = 0; i < sightRadius; i++) - { - getVisibleNeighbours(from, to); - vstd::concatenate(from, to); - vstd::removeDuplicates(from); - } - - logAi->debug("Exploration scan all possible tiles for hero %s", hero.name); - - for(const int3 & tile : from) - { - scanTile(tile); - } - } - - void scanTile(const int3 & tile) - { - if(tile == ourPos - || !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does - return; - - int tilesDiscovered = howManyTilesWillBeDiscovered(tile); - if(!tilesDiscovered) - return; - - auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy); - for(auto goal : waysToVisit) - { - if(goal->evaluationContext.movementCost <= 0.0) // should not happen - continue; - - float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; - - if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much - { - auto obj = cb->getTopObj(tile); - - // picking up resources does not yield any exploration at all. - // if it blocks the way to some explorable tile AIPathfinder will take care of it - if(obj && obj->isBlockedVisitable()) - { - continue; - } - - if(isSafeToVisit(hero, tile)) - { - bestGoal = goal; - bestValue = ourValue; - } - } - } - } - - void getVisibleNeighbours(const std::vector & tiles, std::vector & out) const - { - for(const int3 & tile : tiles) - { - foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) - { - if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) - { - out.push_back(neighbour); - } - }); - } - } - - int howManyTilesWillBeDiscovered(const int3 & pos) const - { - int ret = 0; - int3 npos = int3(0, 0, pos.z); - - const auto & slice = ts->fogOfWarMap[pos.z]; - - for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++) - { - for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++) - { - if(cbp->isInTheMap(npos) - && pos.dist2d(npos) - 0.5 < sightRadius - && !slice[npos.x][npos.y]) - { - if(allowDeadEndCancellation - && !hasReachableneighbour(npos)) - { - continue; - } - - ret++; - } - } - } - - return ret; - } - - bool hasReachableneighbour(const int3 &pos) const - { - for(crint3 dir : int3::getDirs()) - { - int3 tile = pos + dir; - if(cbp->isInTheMap(tile)) - { - auto isAccessible = aip->ah->isTileAccessible(hero, tile); - - if(isAccessible) - return true; - } - } - - return false; - } - }; -} - -bool Explore::operator==(const Explore & other) const -{ - return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; -} - -std::string Explore::completeMessage() const -{ - return "Hero " + hero.get()->getNameTranslated() + " completed exploration"; -} - -TSubgoal Explore::whatToDoToAchieve() -{ - return fh->chooseSolution(getAllPossibleSubgoals()); -} - -TGoalVec Explore::getAllPossibleSubgoals() -{ - TGoalVec ret; - std::vector heroes; - - if(hero) - { - heroes.push_back(hero.h); - } - else - { - //heroes = ai->getUnblockedHeroes(); - heroes = cb->getHeroesInfo(); - vstd::erase_if(heroes, [](const HeroPtr h) - { - if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer - return true; - - if(!ai->isAbleToExplore(h)) - return true; - - return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway - }); - } - - //try to use buildings that uncover map - std::vector objs; - for(auto obj : ai->visitableObjs) - { - if(!vstd::contains(ai->alreadyVisited, obj)) - { - switch(obj->ID.num) - { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - case Obj::CARTOGRAPHER: - objs.push_back(obj); - break; - case Obj::MONOLITH_ONE_WAY_ENTRANCE: - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); - if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) - objs.push_back(obj); - break; - } - } - else - { - switch(obj->ID.num) - { - case Obj::MONOLITH_TWO_WAY: - case Obj::SUBTERRANEAN_GATE: - auto tObj = dynamic_cast(obj); - if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) - break; - for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) - { - if(!cb->getObj(exit)) - { // Always attempt to visit two-way teleports if one of channel exits is not visible - objs.push_back(obj); - break; - } - } - break; - } - } - } - - for(auto h : heroes) - { - for(auto obj : objs) //double loop, performance risk? - { - auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy); - - vstd::concatenate(ret, waysToVisitObj); - } - - TSubgoal goal = exploreNearestNeighbour(h); - - if(!goal->invalid()) - { - ret.push_back(goal); - } - } - - if(ret.empty()) - { - for(auto h : heroes) - { - logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated()); - - TSubgoal goal = explorationNewPoint(h); - - if(goal->invalid()) - { - ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore - } - else - { - ret.push_back(goal); - } - } - } - - //we either don't have hero yet or none of heroes can explore - if((!hero || ret.empty()) && ai->canRecruitAnyHero()) - ret.push_back(sptr(RecruitHero())); - - if(ret.empty()) - { - throw goalFulfilledException(sptr(Explore().sethero(hero))); - } - - return ret; -} - -bool Explore::fulfillsMe(TSubgoal goal) -{ - if(goal->goalType == EXPLORE) - { - if(goal->hero) - return hero == goal->hero; - else - return true; //cancel ALL exploration - } - return false; -} - -TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const -{ - ExplorationHelper scanResult(h, allowGatherArmy); - - for(crint3 dir : int3::getDirs()) - { - int3 tile = hpos + dir; - if(cb->isInTheMap(tile)) - { - scanResult.scanTile(tile); - } - } - - return scanResult.bestGoal; -} - - -TSubgoal Explore::explorationNewPoint(HeroPtr h) const -{ - ExplorationHelper scanResult(h, allowGatherArmy); - - scanResult.scanSector(10); - - if(!scanResult.bestGoal->invalid()) - { - return scanResult.bestGoal; - } - - scanResult.scanMap(); - - return scanResult.bestGoal; -} - - -TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const -{ - TimeCheck tc("where to explore"); - int3 hpos = h->visitablePos(); - - //look for nearby objs -> visit them if they're close enough - const int DIST_LIMIT = 3; - const float COST_LIMIT = .2f; //todo: fine tune - - std::vector nearbyVisitableObjs; - for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map - { - for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y) - { - for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false)) - { - if(ai->isGoodForVisit(obj, h, COST_LIMIT)) - { - nearbyVisitableObjs.push_back(obj); - } - } - } - } - - if(nearbyVisitableObjs.size()) - { - vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles - boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); - - TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false)); - - if(!pickupNearestObj->invalid()) - { - return pickupNearestObj; - } - } - - //check if nearby tiles allow us to reveal anything - this is quick - return explorationBestNeighbour(hpos, h); -} +/* +* Explore.cpp, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#include "StdInc.h" +#include "Goals.h" +#include "../VCAI.h" +#include "../AIUtility.h" +#include "../AIhelper.h" +#include "../FuzzyHelper.h" +#include "../ResourceManager.h" +#include "../BuildingManager.h" +#include "../../../lib/constants/StringConstants.h" +#include "../../../lib/CPlayerState.h" + +using namespace Goals; + +namespace Goals +{ + struct ExplorationHelper + { + HeroPtr hero; + int sightRadius; + float bestValue; + TSubgoal bestGoal; + VCAI * aip; + CCallback * cbp; + const TeamState * ts; + int3 ourPos; + bool allowDeadEndCancellation; + bool allowGatherArmy; + + ExplorationHelper(HeroPtr h, bool gatherArmy) + { + cbp = cb; + aip = ai; + hero = h; + ts = cbp->getPlayerTeam(ai->playerID); + sightRadius = hero->getSightRadius(); + bestGoal = sptr(Goals::Invalid()); + bestValue = 0; + ourPos = h->visitablePos(); + allowDeadEndCancellation = true; + allowGatherArmy = gatherArmy; + } + + void scanSector(int scanRadius) + { + int3 tile = int3(0, 0, ourPos.z); + + const auto & slice = ts->fogOfWarMap[ourPos.z]; + + for(tile.x = ourPos.x - scanRadius; tile.x <= ourPos.x + scanRadius; tile.x++) + { + for(tile.y = ourPos.y - scanRadius; tile.y <= ourPos.y + scanRadius; tile.y++) + { + + if(cbp->isInTheMap(tile) && slice[tile.x][tile.y]) + { + scanTile(tile); + } + } + } + } + + void scanMap() + { + int3 mapSize = cbp->getMapSize(); + int perimeter = 2 * sightRadius * (mapSize.x + mapSize.y); + + std::vector from; + std::vector to; + + from.reserve(perimeter); + to.reserve(perimeter); + + foreach_tile_pos([&](const int3 & pos) + { + if(ts->fogOfWarMap[pos.z][pos.x][pos.y]) + { + bool hasInvisibleNeighbor = false; + + foreach_neighbour(cbp, pos, [&](CCallback * cbp, int3 neighbour) + { + if(!ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) + { + hasInvisibleNeighbor = true; + } + }); + + if(hasInvisibleNeighbor) + from.push_back(pos); + } + }); + + logAi->debug("Exploration scan visible area perimeter for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + + if(!bestGoal->invalid()) + { + return; + } + + allowDeadEndCancellation = false; + + for(int i = 0; i < sightRadius; i++) + { + getVisibleNeighbours(from, to); + vstd::concatenate(from, to); + vstd::removeDuplicates(from); + } + + logAi->debug("Exploration scan all possible tiles for hero %s", hero.name); + + for(const int3 & tile : from) + { + scanTile(tile); + } + } + + void scanTile(const int3 & tile) + { + if(tile == ourPos + || !aip->ah->isTileAccessible(hero, tile)) //shouldn't happen, but it does + return; + + int tilesDiscovered = howManyTilesWillBeDiscovered(tile); + if(!tilesDiscovered) + return; + + auto waysToVisit = aip->ah->howToVisitTile(hero, tile, allowGatherArmy); + for(auto goal : waysToVisit) + { + if(goal->evaluationContext.movementCost <= 0.0) // should not happen + continue; + + float ourValue = (float)tilesDiscovered * tilesDiscovered / goal->evaluationContext.movementCost; + + if(ourValue > bestValue) //avoid costly checks of tiles that don't reveal much + { + auto obj = cb->getTopObj(tile); + + // picking up resources does not yield any exploration at all. + // if it blocks the way to some explorable tile AIPathfinder will take care of it + if(obj && obj->isBlockedVisitable()) + { + continue; + } + + if(isSafeToVisit(hero, tile)) + { + bestGoal = goal; + bestValue = ourValue; + } + } + } + } + + void getVisibleNeighbours(const std::vector & tiles, std::vector & out) const + { + for(const int3 & tile : tiles) + { + foreach_neighbour(cbp, tile, [&](CCallback * cbp, int3 neighbour) + { + if(ts->fogOfWarMap[neighbour.z][neighbour.x][neighbour.y]) + { + out.push_back(neighbour); + } + }); + } + } + + int howManyTilesWillBeDiscovered(const int3 & pos) const + { + int ret = 0; + int3 npos = int3(0, 0, pos.z); + + const auto & slice = ts->fogOfWarMap[pos.z]; + + for(npos.x = pos.x - sightRadius; npos.x <= pos.x + sightRadius; npos.x++) + { + for(npos.y = pos.y - sightRadius; npos.y <= pos.y + sightRadius; npos.y++) + { + if(cbp->isInTheMap(npos) + && pos.dist2d(npos) - 0.5 < sightRadius + && !slice[npos.x][npos.y]) + { + if(allowDeadEndCancellation + && !hasReachableNeighbor(npos)) + { + continue; + } + + ret++; + } + } + } + + return ret; + } + + bool hasReachableNeighbor(const int3 &pos) const + { + for(crint3 dir : int3::getDirs()) + { + int3 tile = pos + dir; + if(cbp->isInTheMap(tile)) + { + auto isAccessible = aip->ah->isTileAccessible(hero, tile); + + if(isAccessible) + return true; + } + } + + return false; + } + }; +} + +bool Explore::operator==(const Explore & other) const +{ + return other.hero.h == hero.h && other.allowGatherArmy == allowGatherArmy; +} + +std::string Explore::completeMessage() const +{ + return "Hero " + hero.get()->getNameTranslated() + " completed exploration"; +} + +TSubgoal Explore::whatToDoToAchieve() +{ + return fh->chooseSolution(getAllPossibleSubgoals()); +} + +TGoalVec Explore::getAllPossibleSubgoals() +{ + TGoalVec ret; + std::vector heroes; + + if(hero) + { + heroes.push_back(hero.h); + } + else + { + //heroes = ai->getUnblockedHeroes(); + heroes = cb->getHeroesInfo(); + vstd::erase_if(heroes, [](const HeroPtr h) + { + if(ai->getGoal(h)->goalType == EXPLORE) //do not reassign hero who is already explorer + return true; + + if(!ai->isAbleToExplore(h)) + return true; + + return !h->movementPointsRemaining(); //saves time, immobile heroes are useless anyway + }); + } + + //try to use buildings that uncover map + std::vector objs; + for(auto obj : ai->visitableObjs) + { + if(!vstd::contains(ai->alreadyVisited, obj)) + { + switch(obj->ID.num) + { + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: + case Obj::CARTOGRAPHER: + objs.push_back(obj); + break; + case Obj::MONOLITH_ONE_WAY_ENTRANCE: + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + assert(ai->knownTeleportChannels.find(tObj->channel) != ai->knownTeleportChannels.end()); + if(TeleportChannel::IMPASSABLE != ai->knownTeleportChannels[tObj->channel]->passability) + objs.push_back(obj); + break; + } + } + else + { + switch(obj->ID.num) + { + case Obj::MONOLITH_TWO_WAY: + case Obj::SUBTERRANEAN_GATE: + auto tObj = dynamic_cast(obj); + if(TeleportChannel::IMPASSABLE == ai->knownTeleportChannels[tObj->channel]->passability) + break; + for(auto exit : ai->knownTeleportChannels[tObj->channel]->exits) + { + if(!cb->getObj(exit)) + { // Always attempt to visit two-way teleports if one of channel exits is not visible + objs.push_back(obj); + break; + } + } + break; + } + } + } + + for(auto h : heroes) + { + for(auto obj : objs) //double loop, performance risk? + { + auto waysToVisitObj = ai->ah->howToVisitObj(h, obj, allowGatherArmy); + + vstd::concatenate(ret, waysToVisitObj); + } + + TSubgoal goal = exploreNearestNeighbour(h); + + if(!goal->invalid()) + { + ret.push_back(goal); + } + } + + if(ret.empty()) + { + for(auto h : heroes) + { + logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated()); + + TSubgoal goal = explorationNewPoint(h); + + if(goal->invalid()) + { + ai->markHeroUnableToExplore(h); //there is no freely accessible tile, do not poll this hero anymore + } + else + { + ret.push_back(goal); + } + } + } + + //we either don't have hero yet or none of heroes can explore + if((!hero || ret.empty()) && ai->canRecruitAnyHero()) + ret.push_back(sptr(RecruitHero())); + + if(ret.empty()) + { + throw goalFulfilledException(sptr(Explore().sethero(hero))); + } + + return ret; +} + +bool Explore::fulfillsMe(TSubgoal goal) +{ + if(goal->goalType == EXPLORE) + { + if(goal->hero) + return hero == goal->hero; + else + return true; //cancel ALL exploration + } + return false; +} + +TSubgoal Explore::explorationBestNeighbour(int3 hpos, HeroPtr h) const +{ + ExplorationHelper scanResult(h, allowGatherArmy); + + for(crint3 dir : int3::getDirs()) + { + int3 tile = hpos + dir; + if(cb->isInTheMap(tile)) + { + scanResult.scanTile(tile); + } + } + + return scanResult.bestGoal; +} + + +TSubgoal Explore::explorationNewPoint(HeroPtr h) const +{ + ExplorationHelper scanResult(h, allowGatherArmy); + + scanResult.scanSector(10); + + if(!scanResult.bestGoal->invalid()) + { + return scanResult.bestGoal; + } + + scanResult.scanMap(); + + return scanResult.bestGoal; +} + + +TSubgoal Explore::exploreNearestNeighbour(HeroPtr h) const +{ + TimeCheck tc("where to explore"); + int3 hpos = h->visitablePos(); + + //look for nearby objs -> visit them if they're close enough + const int DIST_LIMIT = 3; + const float COST_LIMIT = .2f; //todo: fine tune + + std::vector nearbyVisitableObjs; + for(int x = hpos.x - DIST_LIMIT; x <= hpos.x + DIST_LIMIT; ++x) //get only local objects instead of all possible objects on the map + { + for(int y = hpos.y - DIST_LIMIT; y <= hpos.y + DIST_LIMIT; ++y) + { + for(auto obj : cb->getVisitableObjs(int3(x, y, hpos.z), false)) + { + if(ai->isGoodForVisit(obj, h, COST_LIMIT)) + { + nearbyVisitableObjs.push_back(obj); + } + } + } + } + + if(nearbyVisitableObjs.size()) + { + vstd::removeDuplicates(nearbyVisitableObjs); //one object may occupy multiple tiles + boost::sort(nearbyVisitableObjs, CDistanceSorter(h.get())); + + TSubgoal pickupNearestObj = fh->chooseSolution(ai->ah->howToVisitObj(h, nearbyVisitableObjs.back(), false)); + + if(!pickupNearestObj->invalid()) + { + return pickupNearestObj; + } + } + + //check if nearby tiles allow us to reveal anything - this is quick + return explorationBestNeighbour(hpos, h); +} diff --git a/AI/VCAI/Goals/Explore.h b/AI/VCAI/Goals/Explore.h index 5c5b29679..a96de7f29 100644 --- a/AI/VCAI/Goals/Explore.h +++ b/AI/VCAI/Goals/Explore.h @@ -1,66 +1,66 @@ -/* -* Explore.h, part of VCMI engine -* -* Authors: listed in file AUTHORS in main folder -* -* License: GNU General Public License v2.0 or later -* Full text of license available in license.txt file, in main folder -* -*/ -#pragma once - -#include "CGoal.h" - -struct HeroPtr; -class VCAI; -class FuzzyHelper; - -namespace Goals -{ - struct ExplorationHelper; - - class DLL_EXPORT Explore : public CGoal - { - private: - bool allowGatherArmy; - - public: - Explore(bool allowGatherArmy) - : CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy) - { - priority = 1; - } - - Explore() - : Explore(true) - { - } - - Explore(HeroPtr h) - : CGoal(Goals::EXPLORE) - { - hero = h; - priority = 1; - } - TGoalVec getAllPossibleSubgoals() override; - TSubgoal whatToDoToAchieve() override; - std::string completeMessage() const override; - bool fulfillsMe(TSubgoal goal) override; - bool operator==(const Explore & other) const override; - - private: - TSubgoal exploreNearestNeighbour(HeroPtr h) const; - TSubgoal explorationNewPoint(HeroPtr h) const; - TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const; - void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const; - bool hasReachableneighbour(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; - - void getVisibleNeighbours( - const std::vector & tiles, - std::vector & out, - CCallback * cbp, - const TeamState * ts) const; - - int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const; - }; -} +/* +* Explore.h, part of VCMI engine +* +* Authors: listed in file AUTHORS in main folder +* +* License: GNU General Public License v2.0 or later +* Full text of license available in license.txt file, in main folder +* +*/ +#pragma once + +#include "CGoal.h" + +struct HeroPtr; +class VCAI; +class FuzzyHelper; + +namespace Goals +{ + struct ExplorationHelper; + + class DLL_EXPORT Explore : public CGoal + { + private: + bool allowGatherArmy; + + public: + Explore(bool allowGatherArmy) + : CGoal(Goals::EXPLORE), allowGatherArmy(allowGatherArmy) + { + priority = 1; + } + + Explore() + : Explore(true) + { + } + + Explore(HeroPtr h) + : CGoal(Goals::EXPLORE) + { + hero = h; + priority = 1; + } + TGoalVec getAllPossibleSubgoals() override; + TSubgoal whatToDoToAchieve() override; + std::string completeMessage() const override; + bool fulfillsMe(TSubgoal goal) override; + bool operator==(const Explore & other) const override; + + private: + TSubgoal exploreNearestNeighbour(HeroPtr h) const; + TSubgoal explorationNewPoint(HeroPtr h) const; + TSubgoal explorationBestNeighbour(int3 hpos, HeroPtr h) const; + void explorationScanTile(const int3 & tile, ExplorationHelper & scanResult) const; + bool hasReachableNeighbor(const int3 &pos, HeroPtr hero, CCallback * cbp, VCAI * vcai) const; + + void getVisibleNeighbours( + const std::vector & tiles, + std::vector & out, + CCallback * cbp, + const TeamState * ts) const; + + int howManyTilesWillBeDiscovered(const int3 & pos, ExplorationHelper & scanResult) const; + }; +} diff --git a/client/battle/BattleFieldController.cpp b/client/battle/BattleFieldController.cpp index 2206b24cb..cb23064ad 100644 --- a/client/battle/BattleFieldController.cpp +++ b/client/battle/BattleFieldController.cpp @@ -819,6 +819,9 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget) bool BattleFieldController::isTileAttackable(const BattleHex & number) const { + if(!number.isValid()) + return false; + for (auto & elem : occupiableHexes) { if (BattleHex::mutualPosition(elem, number) != -1 || elem == number) diff --git a/client/battle/BattleFieldController.h b/client/battle/BattleFieldController.h index ecb6237af..e14aa5cab 100644 --- a/client/battle/BattleFieldController.h +++ b/client/battle/BattleFieldController.h @@ -1,141 +1,141 @@ -/* - * BattleFieldController.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../../lib/battle/BattleHexArray.h" -#include "../../lib/Point.h" -#include "../gui/CIntObject.h" - -VCMI_LIB_NAMESPACE_BEGIN -class CStack; -class Rect; -VCMI_LIB_NAMESPACE_END - -class BattleHero; -class CAnimation; -class Canvas; -class IImage; -class BattleInterface; - -/// Handles battlefield grid as well as rendering of background layer of battle interface -class BattleFieldController : public CIntObject -{ - BattleInterface & owner; - - std::shared_ptr background; - std::shared_ptr cellBorder; - std::shared_ptr cellUnitMovementHighlight; - std::shared_ptr cellUnitMaxMovementHighlight; - std::shared_ptr cellShade; - std::shared_ptr rangedFullDamageLimitImages; - std::shared_ptr shootingRangeLimitImages; - - std::shared_ptr attackCursors; - std::shared_ptr spellCursors; - - /// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack - std::unique_ptr backgroundWithHexes; - - /// direction which will be used to perform attack with current cursor position - Point currentAttackOriginPoint; - - /// hex currently under mouse hover - BattleHex hoveredHex; - - /// hexes to which currently active stack can move - BattleHexArray occupiableHexes; - - /// hexes that when in front of a unit cause it's amount box to move back - std::array stackCountOutsideHexes; - - void showHighlightedHex(Canvas & to, std::shared_ptr highlight, BattleHex hex, bool darkBorder); - - BattleHexArray getHighlightedHexesForActiveStack(); - BattleHexArray getMovementRangeForHoveredStack(); - BattleHexArray getHighlightedHexesForSpellRange(); - BattleHexArray getHighlightedHexesForMovementTarget(); - - // Range limit highlight helpers - - /// get all hexes within a certain distance of given hex - BattleHexArray getRangeHexes(BattleHex sourceHex, uint8_t distance); - - /// get only hexes at the limit of a range - BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray hexRange, uint8_t distanceToLimit); - - /// calculate if a hex is in range limit and return its index in range - bool IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit); - - /// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists - std::vector> getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray rangeHexes, BattleHexArray rangeLimitHexes); - - /// calculates what image to use as range limit, depending on the direction of neighbours - /// a mask is used internally to mark the directions of all neighbours - /// based on this mask the corresponding image is selected - std::vector> calculateRangeLimitHighlightImages(std::vector> hexesNeighbourDirections, std::shared_ptr limitImages); - - /// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes - void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights); - - void showBackground(Canvas & canvas); - void showBackgroundImage(Canvas & canvas); - void showBackgroundImageWithHexes(Canvas & canvas); - void showHighlightedHexes(Canvas & canvas); - void updateAccessibleHexes(); - - BattleHex getHexAtPosition(Point hoverPosition); - - /// Checks whether selected pixel is transparent, uses local coordinates of a hex - bool isPixelInHex(Point const & position); - size_t selectBattleCursor(BattleHex myNumber); - - void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; - void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override; - void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override; - void clickPressed(const Point & cursorPosition) override; - void showPopupWindow(const Point & cursorPosition) override; - void activate() override; - - void showAll(Canvas & to) override; - void show(Canvas & to) override; - void tick(uint32_t msPassed) override; - - bool receiveEvent(const Point & position, int eventType) const override; - -public: - BattleFieldController(BattleInterface & owner); - - void createHeroes(); - - void redrawBackgroundWithHexes(); - void renderBattlefield(Canvas & canvas); - - /// Returns position of hex relative to owner (BattleInterface) - Rect hexPositionLocal(BattleHex hex) const; - - /// Returns position of hex relative to game window - Rect hexPositionAbsolute(BattleHex hex) const; - - /// Returns ID of currently hovered hex or BattleHex::INVALID if none - BattleHex getHoveredHex(); - - /// Returns the currently hovered stack - const CStack* getHoveredStack(); - - /// returns true if selected tile can be attacked in melee by current stack - bool isTileAttackable(const BattleHex & number) const; - - /// returns true if stack should render its stack count image in default position - outside own hex - bool stackCountOutsideHex(const BattleHex & number) const; - - BattleHex::EDir selectAttackDirection(BattleHex myNumber); - - BattleHex fromWhichHexAttack(BattleHex myNumber); -}; +/* + * BattleFieldController.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../../lib/battle/BattleHexArray.h" +#include "../../lib/Point.h" +#include "../gui/CIntObject.h" + +VCMI_LIB_NAMESPACE_BEGIN +class CStack; +class Rect; +VCMI_LIB_NAMESPACE_END + +class BattleHero; +class CAnimation; +class Canvas; +class IImage; +class BattleInterface; + +/// Handles battlefield grid as well as rendering of background layer of battle interface +class BattleFieldController : public CIntObject +{ + BattleInterface & owner; + + std::shared_ptr background; + std::shared_ptr cellBorder; + std::shared_ptr cellUnitMovementHighlight; + std::shared_ptr cellUnitMaxMovementHighlight; + std::shared_ptr cellShade; + std::shared_ptr rangedFullDamageLimitImages; + std::shared_ptr shootingRangeLimitImages; + + std::shared_ptr attackCursors; + std::shared_ptr spellCursors; + + /// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack + std::unique_ptr backgroundWithHexes; + + /// direction which will be used to perform attack with current cursor position + Point currentAttackOriginPoint; + + /// hex currently under mouse hover + BattleHex hoveredHex; + + /// hexes to which currently active stack can move + BattleHexArray occupiableHexes; + + /// hexes that when in front of a unit cause it's amount box to move back + std::array stackCountOutsideHexes; + + void showHighlightedHex(Canvas & to, std::shared_ptr highlight, BattleHex hex, bool darkBorder); + + BattleHexArray getHighlightedHexesForActiveStack(); + BattleHexArray getMovementRangeForHoveredStack(); + BattleHexArray getHighlightedHexesForSpellRange(); + BattleHexArray getHighlightedHexesForMovementTarget(); + + // Range limit highlight helpers + + /// get all hexes within a certain distance of given hex + BattleHexArray getRangeHexes(BattleHex sourceHex, uint8_t distance); + + /// get only hexes at the limit of a range + BattleHexArray getRangeLimitHexes(BattleHex hoveredHex, BattleHexArray hexRange, uint8_t distanceToLimit); + + /// calculate if a hex is in range limit and return its index in range + bool IsHexInRangeLimit(BattleHex hex, BattleHexArray & rangeLimitHexes, int * hexIndexInRangeLimit); + + /// get an array that has for each hex in range, an array with all directions where an outside neighbour hex exists + std::vector> getOutsideNeighbourDirectionsForLimitHexes(BattleHexArray rangeHexes, BattleHexArray rangeLimitHexes); + + /// calculates what image to use as range limit, depending on the direction of neighbours + /// a mask is used internally to mark the directions of all neighbours + /// based on this mask the corresponding image is selected + std::vector> calculateRangeLimitHighlightImages(std::vector> hexesNeighbourDirections, std::shared_ptr limitImages); + + /// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes + void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr rangeLimitImages, BattleHexArray & rangeLimitHexes, std::vector> & rangeLimitHexesHighlights); + + void showBackground(Canvas & canvas); + void showBackgroundImage(Canvas & canvas); + void showBackgroundImageWithHexes(Canvas & canvas); + void showHighlightedHexes(Canvas & canvas); + void updateAccessibleHexes(); + + BattleHex getHexAtPosition(Point hoverPosition); + + /// Checks whether selected pixel is transparent, uses local coordinates of a hex + bool isPixelInHex(Point const & position); + size_t selectBattleCursor(BattleHex myNumber); + + void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override; + void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override; + void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override; + void clickPressed(const Point & cursorPosition) override; + void showPopupWindow(const Point & cursorPosition) override; + void activate() override; + + void showAll(Canvas & to) override; + void show(Canvas & to) override; + void tick(uint32_t msPassed) override; + + bool receiveEvent(const Point & position, int eventType) const override; + +public: + BattleFieldController(BattleInterface & owner); + + void createHeroes(); + + void redrawBackgroundWithHexes(); + void renderBattlefield(Canvas & canvas); + + /// Returns position of hex relative to owner (BattleInterface) + Rect hexPositionLocal(BattleHex hex) const; + + /// Returns position of hex relative to game window + Rect hexPositionAbsolute(BattleHex hex) const; + + /// Returns ID of currently hovered hex or BattleHex::INVALID if none + BattleHex getHoveredHex(); + + /// Returns the currently hovered stack + const CStack* getHoveredStack(); + + /// returns true if selected tile can be attacked in melee by current stack + bool isTileAttackable(const BattleHex & number) const; + + /// returns true if stack should render its stack count image in default position - outside own hex + bool stackCountOutsideHex(const BattleHex & number) const; + + BattleHex::EDir selectAttackDirection(BattleHex myNumber); + + BattleHex fromWhichHexAttack(BattleHex myNumber); +}; diff --git a/client/battle/BattleInterface.h b/client/battle/BattleInterface.h index 219143b06..43a529fd4 100644 --- a/client/battle/BattleInterface.h +++ b/client/battle/BattleInterface.h @@ -1,232 +1,232 @@ -/* - * BattleInterface.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "BattleConstants.h" -#include "../gui/CIntObject.h" -#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation -#include "../ConditionalWait.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class CCreatureSet; -class CGHeroInstance; -class CStack; -struct BattleResult; -struct BattleSpellCast; -struct CObstacleInstance; -struct SetStackEffect; -class BattleAction; -class CGTownInstance; -struct CatapultAttack; -struct BattleTriggerEffect; -struct BattleHex; -struct InfoAboutHero; -class ObstacleChanges; -class CPlayerBattleCallback; - -VCMI_LIB_NAMESPACE_END - -class BattleHero; -class Canvas; -class BattleResultWindow; -class StackQueue; -class CPlayerInterface; -struct BattleEffect; -class IImage; -class StackQueue; - -class BattleProjectileController; -class BattleSiegeController; -class BattleObstacleController; -class BattleFieldController; -class BattleRenderer; -class BattleWindow; -class BattleStacksController; -class BattleActionsController; -class BattleEffectsController; -class BattleConsole; - -/// Small struct which contains information about the id of the attacked stack, the damage dealt,... -struct StackAttackedInfo -{ - const CStack *defender; - const CStack *attacker; - - int64_t damageDealt; - uint32_t amountKilled; - SpellID spellEffect; - - bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack - bool killed; //if true, stack has been killed - bool rebirth; //if true, play rebirth animation after all - bool cloneKilled; - bool fireShield; -}; - -struct StackAttackInfo -{ - const CStack *attacker; - const CStack *defender; - std::vector< const CStack *> secondaryDefender; - - SpellID spellEffect; - BattleHex tile; - - bool indirectAttack; - bool lucky; - bool unlucky; - bool deathBlow; - bool lifeDrain; -}; - -/// Main class for battles, responsible for relaying information from server to various battle entities -class BattleInterface -{ - using AwaitingAnimationAction = std::function; - - struct AwaitingAnimationEvents { - AwaitingAnimationAction action; - EAnimationEvents event; - }; - - /// Conditional variables that are set depending on ongoing animations on the battlefield - ConditionalWait ongoingAnimationsState; - - /// List of events that are waiting to be triggered - std::vector awaitingEvents; - - /// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players - std::shared_ptr tacticianInterface; - - /// attacker interface, not null if attacker is human in our vcmiclient - std::shared_ptr attackerInt; - - /// defender interface, not null if attacker is human in our vcmiclient - std::shared_ptr defenderInt; - - /// if set to true, battle is still starting and waiting for intro sound to end / key press from player - bool battleOpeningDelayActive; - - /// ID of ongoing battle - BattleID battleID; - - void playIntroSoundAndUnlockInterface(); - void onIntroSoundPlayed(); -public: - /// copy of initial armies (for result window) - const CCreatureSet *army1; - const CCreatureSet *army2; - - std::shared_ptr windowObject; - std::shared_ptr console; - - /// currently active player interface - std::shared_ptr curInt; - - const CGHeroInstance *attackingHeroInstance; - const CGHeroInstance *defendingHeroInstance; - - bool tacticsMode; - ui32 round; - - std::unique_ptr projectilesController; - std::unique_ptr siegeController; - std::unique_ptr obstacleController; - std::unique_ptr fieldController; - std::unique_ptr stacksController; - std::unique_ptr actionsController; - std::unique_ptr effectsController; - - std::shared_ptr attackingHero; - std::shared_ptr defendingHero; - - bool openingPlaying() const; - void openingEnd(); - - bool makingTurn() const; - - BattleID getBattleID() const; - std::shared_ptr getBattle() const; - - BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); - ~BattleInterface(); - - void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player - void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all - void requestAutofightingAIToTakeAction(); - - void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE); - void sendCommand(BattleAction command, const CStack * actor = nullptr); - - const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell - - void showInterface(Canvas & to); - - void setHeroAnimation(BattleSide side, EHeroAnimType phase); - - void executeSpellCast(); //called when a hero casts a spell - - void appendBattleLog(const std::string & newEntry); - - void redrawBattlefield(); //refresh GUI after changing stack range / grid settings - CPlayerInterface *getCurrentPlayerInterface() const; - - void tacticNextStack(const CStack *current); - void tacticPhaseEnd(); - - void setBattleQueueVisibility(bool visible); - void setStickyHeroWindowsVisibility(bool visible); - void setStickyQuickSpellWindowVisibility(bool visible); - - void endNetwork(); - void executeStagedAnimations(); - void executeAnimationStage( EAnimationEvents event); - void onAnimationsStarted(); - void onAnimationsFinished(); - void waitForAnimations(); - bool hasAnimations(); - void checkForAnimations(); - void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action); - - //call-ins - void startAction(const BattleAction & action); - void stackReset(const CStack * stack); - void stackAdded(const CStack * stack); //new stack appeared on battlefield - void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled - void stackActivated(const CStack *stack); //active stack has been changed - void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport); //stack with id number moved to destHex - void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked - void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest - void newRoundFirst(); - void newRound(); //called when round is ended; - void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls - void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed - void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell - void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks - void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook - - void displayBattleLog(const std::vector & battleLog); - - void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit); - void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation - void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation - void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation - - void endAction(const BattleAction & action); - - void obstaclePlaced(const std::vector> oi); - void obstacleRemoved(const std::vector & obstacles); - - void gateStateChanged(const EGateState state); - - const CGHeroInstance *currentHero() const; - InfoAboutHero enemyHero() const; -}; +/* + * BattleInterface.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "BattleConstants.h" +#include "../gui/CIntObject.h" +#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation +#include "../ConditionalWait.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class CCreatureSet; +class CGHeroInstance; +class CStack; +struct BattleResult; +struct BattleSpellCast; +struct CObstacleInstance; +struct SetStackEffect; +class BattleAction; +class CGTownInstance; +struct CatapultAttack; +struct BattleTriggerEffect; +struct BattleHex; +struct InfoAboutHero; +class ObstacleChanges; +class CPlayerBattleCallback; + +VCMI_LIB_NAMESPACE_END + +class BattleHero; +class Canvas; +class BattleResultWindow; +class StackQueue; +class CPlayerInterface; +struct BattleEffect; +class IImage; +class StackQueue; + +class BattleProjectileController; +class BattleSiegeController; +class BattleObstacleController; +class BattleFieldController; +class BattleRenderer; +class BattleWindow; +class BattleStacksController; +class BattleActionsController; +class BattleEffectsController; +class BattleConsole; + +/// Small struct which contains information about the id of the attacked stack, the damage dealt,... +struct StackAttackedInfo +{ + const CStack *defender; + const CStack *attacker; + + int64_t damageDealt; + uint32_t amountKilled; + SpellID spellEffect; + + bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack + bool killed; //if true, stack has been killed + bool rebirth; //if true, play rebirth animation after all + bool cloneKilled; + bool fireShield; +}; + +struct StackAttackInfo +{ + const CStack *attacker; + const CStack *defender; + std::vector< const CStack *> secondaryDefender; + + SpellID spellEffect; + BattleHex tile; + + bool indirectAttack; + bool lucky; + bool unlucky; + bool deathBlow; + bool lifeDrain; +}; + +/// Main class for battles, responsible for relaying information from server to various battle entities +class BattleInterface +{ + using AwaitingAnimationAction = std::function; + + struct AwaitingAnimationEvents { + AwaitingAnimationAction action; + EAnimationEvents event; + }; + + /// Conditional variables that are set depending on ongoing animations on the battlefield + ConditionalWait ongoingAnimationsState; + + /// List of events that are waiting to be triggered + std::vector awaitingEvents; + + /// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players + std::shared_ptr tacticianInterface; + + /// attacker interface, not null if attacker is human in our vcmiclient + std::shared_ptr attackerInt; + + /// defender interface, not null if attacker is human in our vcmiclient + std::shared_ptr defenderInt; + + /// if set to true, battle is still starting and waiting for intro sound to end / key press from player + bool battleOpeningDelayActive; + + /// ID of ongoing battle + BattleID battleID; + + void playIntroSoundAndUnlockInterface(); + void onIntroSoundPlayed(); +public: + /// copy of initial armies (for result window) + const CCreatureSet *army1; + const CCreatureSet *army2; + + std::shared_ptr windowObject; + std::shared_ptr console; + + /// currently active player interface + std::shared_ptr curInt; + + const CGHeroInstance *attackingHeroInstance; + const CGHeroInstance *defendingHeroInstance; + + bool tacticsMode; + ui32 round; + + std::unique_ptr projectilesController; + std::unique_ptr siegeController; + std::unique_ptr obstacleController; + std::unique_ptr fieldController; + std::unique_ptr stacksController; + std::unique_ptr actionsController; + std::unique_ptr effectsController; + + std::shared_ptr attackingHero; + std::shared_ptr defendingHero; + + bool openingPlaying() const; + void openingEnd(); + + bool makingTurn() const; + + BattleID getBattleID() const; + std::shared_ptr getBattle() const; + + BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); + ~BattleInterface(); + + void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player + void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all + void requestAutofightingAIToTakeAction(); + + void giveCommand(EActionType action, BattleHex tile = BattleHex(), SpellID spell = SpellID::NONE); + void sendCommand(BattleAction command, const CStack * actor = nullptr); + + const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell + + void showInterface(Canvas & to); + + void setHeroAnimation(BattleSide side, EHeroAnimType phase); + + void executeSpellCast(); //called when a hero casts a spell + + void appendBattleLog(const std::string & newEntry); + + void redrawBattlefield(); //refresh GUI after changing stack range / grid settings + CPlayerInterface *getCurrentPlayerInterface() const; + + void tacticNextStack(const CStack *current); + void tacticPhaseEnd(); + + void setBattleQueueVisibility(bool visible); + void setStickyHeroWindowsVisibility(bool visible); + void setStickyQuickSpellWindowVisibility(bool visible); + + void endNetwork(); + void executeStagedAnimations(); + void executeAnimationStage( EAnimationEvents event); + void onAnimationsStarted(); + void onAnimationsFinished(); + void waitForAnimations(); + bool hasAnimations(); + void checkForAnimations(); + void addToAnimationStage( EAnimationEvents event, const AwaitingAnimationAction & action); + + //call-ins + void startAction(const BattleAction & action); + void stackReset(const CStack * stack); + void stackAdded(const CStack * stack); //new stack appeared on battlefield + void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled + void stackActivated(const CStack *stack); //active stack has been changed + void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance, bool teleport); //stack with id number moved to destHex + void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked + void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest + void newRoundFirst(); + void newRound(); //called when round is ended; + void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls + void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed + void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell + void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks + void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook + + void displayBattleLog(const std::vector & battleLog); + + void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit); + void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation + void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation + void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation + + void endAction(const BattleAction & action); + + void obstaclePlaced(const std::vector> oi); + void obstacleRemoved(const std::vector & obstacles); + + void gateStateChanged(const EGateState state); + + const CGHeroInstance *currentHero() const; + InfoAboutHero enemyHero() const; +}; diff --git a/client/battle/BattleStacksController.h b/client/battle/BattleStacksController.h index 3f6862343..f13f9fca4 100644 --- a/client/battle/BattleStacksController.h +++ b/client/battle/BattleStacksController.h @@ -1,149 +1,149 @@ -/* - * BattleStacksController.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../render/ColorFilter.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleHex; -class BattleHexArray; -class BattleAction; -class CStack; -class CSpell; -class SpellID; -class Point; - -VCMI_LIB_NAMESPACE_END - -struct StackAttackedInfo; -struct StackAttackInfo; - -class ColorFilter; -class Canvas; -class BattleInterface; -class BattleAnimation; -class CreatureAnimation; -class BattleAnimation; -class BattleRenderer; -class IImage; - -struct BattleStackFilterEffect -{ - ColorFilter effect; - const CStack * target; - const CSpell * source; - bool persistent; -}; - -/// Class responsible for handling stacks in battle -/// Handles ordering of stacks animation -/// As well as rendering of stacks, their amount boxes -/// And any other effect applied to stacks -class BattleStacksController -{ - BattleInterface & owner; - - std::shared_ptr amountNormal; - std::shared_ptr amountNegative; - std::shared_ptr amountPositive; - std::shared_ptr amountEffNeutral; - - /// currently displayed animations - std::vector currentAnimations; - - /// currently active color effects on stacks, in order of their addition (which corresponds to their apply order) - std::vector stackFilterEffects; - - /// animations of creatures from fighting armies (order by BattleInfo's stacks' ID) - std::map> stackAnimation; - - /// //TODO: move it to battle callback - std::map stackFacingRight; - - /// Stacks have amount box hidden due to ongoing animations - std::set stackAmountBoxHidden; - - /// currently active stack; nullptr - no one - const CStack *activeStack; - - /// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation - std::vector mouseHoveredStacks; - - ///when animation is playing, we should wait till the end to make the next stack active; nullptr of none - const CStack *stackToActivate; - - /// for giving IDs for animations - ui32 animIDhelper; - - bool stackNeedsAmountBox(const CStack * stack) const; - void showStackAmountBox(Canvas & canvas, const CStack * stack); - BattleHex getStackCurrentPosition(const CStack * stack) const; - - std::shared_ptr getStackAmountBox(const CStack * stack); - - void removeExpiredColorFilters(); - - void initializeBattleAnimations(); - void tickFrameBattleAnimations(uint32_t msPassed); - - void updateBattleAnimations(uint32_t msPassed); - - std::vector selectHoveredStacks(); - - bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender); - -public: - BattleStacksController(BattleInterface & owner); - - bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const; - bool facingRight(const CStack * stack) const; - - void stackReset(const CStack * stack); - void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield - void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled - void stackActivated(const CStack *stack); //active stack has been changed - void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex - void stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex - void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked - void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest - - void startAction(const BattleAction & action); - void endAction(const BattleAction & action); - - void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack - - void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack - - void setActiveStack(const CStack *stack); - - void showAliveStack(Canvas & canvas, const CStack * stack); - void showStack(Canvas & canvas, const CStack * stack); - - void updateHoveredStacks(); - - void collectRenderableObjects(BattleRenderer & renderer); - - /// Adds new color filter effect targeting stack - /// Effect will last as long as stack is affected by specified spell (unless effect is persistent) - /// If effect from same (target, source) already exists, it will be updated - void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent); - void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims - - const CStack* getActiveStack() const; - const std::vector getHoveredStacksUnitIds() const; - - void tick(uint32_t msPassed); - - /// returns position of animation needed to place stack in specific hex - Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const; - - friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations -}; +/* + * BattleStacksController.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../render/ColorFilter.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleHex; +class BattleHexArray; +class BattleAction; +class CStack; +class CSpell; +class SpellID; +class Point; + +VCMI_LIB_NAMESPACE_END + +struct StackAttackedInfo; +struct StackAttackInfo; + +class ColorFilter; +class Canvas; +class BattleInterface; +class BattleAnimation; +class CreatureAnimation; +class BattleAnimation; +class BattleRenderer; +class IImage; + +struct BattleStackFilterEffect +{ + ColorFilter effect; + const CStack * target; + const CSpell * source; + bool persistent; +}; + +/// Class responsible for handling stacks in battle +/// Handles ordering of stacks animation +/// As well as rendering of stacks, their amount boxes +/// And any other effect applied to stacks +class BattleStacksController +{ + BattleInterface & owner; + + std::shared_ptr amountNormal; + std::shared_ptr amountNegative; + std::shared_ptr amountPositive; + std::shared_ptr amountEffNeutral; + + /// currently displayed animations + std::vector currentAnimations; + + /// currently active color effects on stacks, in order of their addition (which corresponds to their apply order) + std::vector stackFilterEffects; + + /// animations of creatures from fighting armies (order by BattleInfo's stacks' ID) + std::map> stackAnimation; + + /// //TODO: move it to battle callback + std::map stackFacingRight; + + /// Stacks have amount box hidden due to ongoing animations + std::set stackAmountBoxHidden; + + /// currently active stack; nullptr - no one + const CStack *activeStack; + + /// stacks or their battle queue images below mouse pointer (multiple stacks possible while spellcasting), used for border animation + std::vector mouseHoveredStacks; + + ///when animation is playing, we should wait till the end to make the next stack active; nullptr of none + const CStack *stackToActivate; + + /// for giving IDs for animations + ui32 animIDhelper; + + bool stackNeedsAmountBox(const CStack * stack) const; + void showStackAmountBox(Canvas & canvas, const CStack * stack); + BattleHex getStackCurrentPosition(const CStack * stack) const; + + std::shared_ptr getStackAmountBox(const CStack * stack); + + void removeExpiredColorFilters(); + + void initializeBattleAnimations(); + void tickFrameBattleAnimations(uint32_t msPassed); + + void updateBattleAnimations(uint32_t msPassed); + + std::vector selectHoveredStacks(); + + bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender); + +public: + BattleStacksController(BattleInterface & owner); + + bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const; + bool facingRight(const CStack * stack) const; + + void stackReset(const CStack * stack); + void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield + void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled + void stackActivated(const CStack *stack); //active stack has been changed + void stackMoved(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex + void stackTeleported(const CStack *stack, const BattleHexArray & destHex, int distance); //stack with id number moved to destHex + void stacksAreAttacked(std::vector attackedInfos); //called when a certain amount of stacks has been attacked + void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest + + void startAction(const BattleAction & action); + void endAction(const BattleAction & action); + + void deactivateStack(); //copy activeStack to stackToActivate, then set activeStack to nullptr to temporary disable current stack + + void activateStack(); //copy stackToActivate to activeStack to enable controls of the stack + + void setActiveStack(const CStack *stack); + + void showAliveStack(Canvas & canvas, const CStack * stack); + void showStack(Canvas & canvas, const CStack * stack); + + void updateHoveredStacks(); + + void collectRenderableObjects(BattleRenderer & renderer); + + /// Adds new color filter effect targeting stack + /// Effect will last as long as stack is affected by specified spell (unless effect is persistent) + /// If effect from same (target, source) already exists, it will be updated + void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent); + void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims + + const CStack* getActiveStack() const; + const std::vector getHoveredStacksUnitIds() const; + + void tick(uint32_t msPassed); + + /// returns position of animation needed to place stack in specific hex + Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const; + + friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations +}; diff --git a/include/vstd/RNG.h b/include/vstd/RNG.h index d42ba0a73..9762e730d 100644 --- a/include/vstd/RNG.h +++ b/include/vstd/RNG.h @@ -1,104 +1,104 @@ -/* - * RNG.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -VCMI_LIB_NAMESPACE_BEGIN - -namespace vstd -{ - -class DLL_LINKAGE RNG -{ -public: - virtual ~RNG() = default; - - /// Returns random number in range [lower, upper] - virtual int nextInt(int lower, int upper) = 0; - - /// Returns random number in range [lower, upper] - virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0; - - /// Returns random number in range [lower, upper] - virtual double nextDouble(double lower, double upper) = 0; - - /// Returns random number in range [0, upper] - virtual int nextInt(int upper) = 0; - - /// Returns random number in range [0, upper] - virtual int64_t nextInt64(int64_t upper) = 0; - - /// Returns random number in range [0, upper] - virtual double nextDouble(double upper) = 0; - - /// Generates an integer between 0 and the maximum value it can hold. - /// Should be only used for seeding other generators - virtual int nextInt() = 0; - - /// Returns integer using binomial distribution - /// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts - virtual int nextBinomialInt(int coinsCount, double coinChance) = 0; -}; - -} - -namespace RandomGeneratorUtil -{ - template - auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) - { - if(container.empty()) - throw std::runtime_error("Unable to select random item from empty container!"); - - return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); - } - - template - auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) - { - if(container.empty()) - throw std::runtime_error("Unable to select random item from empty container!"); - - return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); - } - - template - size_t nextItemWeighted(Container & container, vstd::RNG & rand) - { - assert(!container.empty()); - - int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0); - assert(totalWeight > 0); - - int64_t roll = rand.nextInt64(0, totalWeight - 1); - - for (size_t i = 0; i < container.size(); ++i) - { - roll -= container[i]; - if(roll < 0) - return i; - } - return container.size() - 1; - } - - template - void randomShuffle(Container & container, vstd::RNG & rand) - { - int64_t n = std::distance(container.begin(), container.end()); - - for(int64_t i = n - 1; i > 0; --i) - { - auto randIndex = rand.nextInt64(0, i); - std::swap(*(container.begin() + i), *(container.begin() + randIndex)); - } - } -} - -VCMI_LIB_NAMESPACE_END +/* + * RNG.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +namespace vstd +{ + +class DLL_LINKAGE RNG +{ +public: + virtual ~RNG() = default; + + /// Returns random number in range [lower, upper] + virtual int nextInt(int lower, int upper) = 0; + + /// Returns random number in range [lower, upper] + virtual int64_t nextInt64(int64_t lower, int64_t upper) = 0; + + /// Returns random number in range [lower, upper] + virtual double nextDouble(double lower, double upper) = 0; + + /// Returns random number in range [0, upper] + virtual int nextInt(int upper) = 0; + + /// Returns random number in range [0, upper] + virtual int64_t nextInt64(int64_t upper) = 0; + + /// Returns random number in range [0, upper] + virtual double nextDouble(double upper) = 0; + + /// Generates an integer between 0 and the maximum value it can hold. + /// Should be only used for seeding other generators + virtual int nextInt() = 0; + + /// Returns integer using binomial distribution + /// returned value is number of successfull coin flips with chance 'coinChance' out of 'coinsCount' attempts + virtual int nextBinomialInt(int coinsCount, double coinChance) = 0; +}; + +} + +namespace RandomGeneratorUtil +{ + template + auto nextItem(const Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); + } + + template + auto nextItem(Container & container, vstd::RNG & rand) -> decltype(std::begin(container)) + { + if(container.empty()) + throw std::runtime_error("Unable to select random item from empty container!"); + + return std::next(container.begin(), rand.nextInt64(0, container.size() - 1)); + } + + template + size_t nextItemWeighted(Container & container, vstd::RNG & rand) + { + assert(!container.empty()); + + int64_t totalWeight = std::accumulate(container.begin(), container.end(), 0); + assert(totalWeight > 0); + + int64_t roll = rand.nextInt64(0, totalWeight - 1); + + for (size_t i = 0; i < container.size(); ++i) + { + roll -= container[i]; + if(roll < 0) + return i; + } + return container.size() - 1; + } + + template + void randomShuffle(Container & container, vstd::RNG & rand) + { + int64_t n = std::distance(container.begin(), container.end()); + + for(int64_t i = n - 1; i > 0; --i) + { + auto randIndex = rand.nextInt64(0, i); + std::swap(*(container.begin() + i), *(container.begin() + randIndex)); + } + } +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index 7746aebb2..583299e8c 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -1,101 +1,101 @@ -/* - * BattleFieldHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include -#include "BattleFieldHandler.h" -#include "json/JsonBonus.h" - -VCMI_LIB_NAMESPACE_BEGIN - -std::shared_ptr BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) -{ - assert(identifier.find(':') == std::string::npos); - - auto info = std::make_shared(BattleField(index), identifier); - - info->modScope = scope; - info->graphics = ImagePath::fromJson(json["graphics"]); - info->icon = json["icon"].String(); - info->name = json["name"].String(); - for(const auto & b : json["bonuses"].Vector()) - { - auto bonus = JsonUtils::parseBonus(b); - - bonus->source = BonusSource::TERRAIN_OVERLAY; - bonus->sid = BonusSourceID(info->getId()); - bonus->duration = BonusDuration::ONE_BATTLE; - - info->bonuses.push_back(bonus); - } - - info->isSpecial = json["isSpecial"].Bool(); - for(auto node : json["impassableHexes"].Vector()) - info->impassableHexes.insert(node.Integer()); - - info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]); - info->musicFilename = AudioPath::fromJson(json["music"]); - - return info; -} - -std::vector BattleFieldHandler::loadLegacyData() -{ - return std::vector(); -} - -const std::vector & BattleFieldHandler::getTypeNames() const -{ - static const auto types = std::vector { "battlefield" }; - - return types; -} - -int32_t BattleFieldInfo::getIndex() const -{ - return battlefield.getNum(); -} - -int32_t BattleFieldInfo::getIconIndex() const -{ - return iconIndex; -} - -std::string BattleFieldInfo::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string BattleFieldInfo::getModScope() const -{ - return modScope; -} - -std::string BattleFieldInfo::getNameTextID() const -{ - return name; -} - -std::string BattleFieldInfo::getNameTranslated() const -{ - return name; // TODO? -} - -void BattleFieldInfo::registerIcons(const IconRegistar & cb) const -{ - //cb(getIconIndex(), "BATTLEFIELD", icon); -} - -BattleField BattleFieldInfo::getId() const -{ - return battlefield; -} - -VCMI_LIB_NAMESPACE_END +/* + * BattleFieldHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include +#include "BattleFieldHandler.h" +#include "json/JsonBonus.h" + +VCMI_LIB_NAMESPACE_BEGIN + +std::shared_ptr BattleFieldHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + + auto info = std::make_shared(BattleField(index), identifier); + + info->modScope = scope; + info->graphics = ImagePath::fromJson(json["graphics"]); + info->icon = json["icon"].String(); + info->name = json["name"].String(); + for(const auto & b : json["bonuses"].Vector()) + { + auto bonus = JsonUtils::parseBonus(b); + + bonus->source = BonusSource::TERRAIN_OVERLAY; + bonus->sid = BonusSourceID(info->getId()); + bonus->duration = BonusDuration::ONE_BATTLE; + + info->bonuses.push_back(bonus); + } + + info->isSpecial = json["isSpecial"].Bool(); + for(auto node : json["impassableHexes"].Vector()) + info->impassableHexes.insert(node.Integer()); + + info->openingSoundFilename = AudioPath::fromJson(json["openingSound"]); + info->musicFilename = AudioPath::fromJson(json["music"]); + + return info; +} + +std::vector BattleFieldHandler::loadLegacyData() +{ + return std::vector(); +} + +const std::vector & BattleFieldHandler::getTypeNames() const +{ + static const auto types = std::vector { "battlefield" }; + + return types; +} + +int32_t BattleFieldInfo::getIndex() const +{ + return battlefield.getNum(); +} + +int32_t BattleFieldInfo::getIconIndex() const +{ + return iconIndex; +} + +std::string BattleFieldInfo::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string BattleFieldInfo::getModScope() const +{ + return modScope; +} + +std::string BattleFieldInfo::getNameTextID() const +{ + return name; +} + +std::string BattleFieldInfo::getNameTranslated() const +{ + return name; // TODO? +} + +void BattleFieldInfo::registerIcons(const IconRegistar & cb) const +{ + //cb(getIconIndex(), "BATTLEFIELD", icon); +} + +BattleField BattleFieldInfo::getId() const +{ + return battlefield; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/BattleFieldHandler.h b/lib/BattleFieldHandler.h index 583b350fd..9e19213a7 100644 --- a/lib/BattleFieldHandler.h +++ b/lib/BattleFieldHandler.h @@ -1,80 +1,80 @@ -/* - * BattleFieldHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include "bonuses/Bonus.h" -#include "GameConstants.h" -#include "IHandlerBase.h" -#include "battle/BattleHexArray.h" -#include "filesystem/ResourcePath.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class BattleFieldInfo : public EntityT -{ -public: - BattleField battlefield; - std::vector> bonuses; - bool isSpecial; - ImagePath graphics; - std::string name; - std::string modScope; - std::string identifier; - std::string icon; - si32 iconIndex; - BattleHexArray impassableHexes; - AudioPath openingSoundFilename; - AudioPath musicFilename; - - BattleFieldInfo() - : BattleFieldInfo(BattleField::NONE, "") - { - } - - BattleFieldInfo(BattleField battlefield, std::string identifier): - isSpecial(false), - battlefield(battlefield), - identifier(identifier), - iconIndex(battlefield.getNum()), - name(identifier) - { - } - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - std::string getNameTextID() const override; - std::string getNameTranslated() const override; - void registerIcons(const IconRegistar & cb) const override; - BattleField getId() const override; -}; - -class DLL_LINKAGE BattleFieldService : public EntityServiceT -{ -public: -}; - -class BattleFieldHandler : public CHandlerBase -{ -public: - std::shared_ptr loadFromJson( - const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - const std::vector & getTypeNames() const override; - std::vector loadLegacyData() override; -}; - -VCMI_LIB_NAMESPACE_END +/* + * BattleFieldHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include +#include +#include "bonuses/Bonus.h" +#include "GameConstants.h" +#include "IHandlerBase.h" +#include "battle/BattleHexArray.h" +#include "filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class BattleFieldInfo : public EntityT +{ +public: + BattleField battlefield; + std::vector> bonuses; + bool isSpecial; + ImagePath graphics; + std::string name; + std::string modScope; + std::string identifier; + std::string icon; + si32 iconIndex; + BattleHexArray impassableHexes; + AudioPath openingSoundFilename; + AudioPath musicFilename; + + BattleFieldInfo() + : BattleFieldInfo(BattleField::NONE, "") + { + } + + BattleFieldInfo(BattleField battlefield, std::string identifier): + isSpecial(false), + battlefield(battlefield), + identifier(identifier), + iconIndex(battlefield.getNum()), + name(identifier) + { + } + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + std::string getNameTextID() const override; + std::string getNameTranslated() const override; + void registerIcons(const IconRegistar & cb) const override; + BattleField getId() const override; +}; + +class DLL_LINKAGE BattleFieldService : public EntityServiceT +{ +public: +}; + +class BattleFieldHandler : public CHandlerBase +{ +public: + std::shared_ptr loadFromJson( + const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/ObstacleHandler.cpp b/lib/ObstacleHandler.cpp index c438773ca..d21fba7ab 100644 --- a/lib/ObstacleHandler.cpp +++ b/lib/ObstacleHandler.cpp @@ -1,129 +1,129 @@ -/* - * ObstacleHandler.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "ObstacleHandler.h" -#include "BattleFieldHandler.h" -#include "json/JsonNode.h" -#include "modding/IdentifierStorage.h" -#include "VCMI_Lib.h" - -VCMI_LIB_NAMESPACE_BEGIN - -int32_t ObstacleInfo::getIndex() const -{ - return obstacle.getNum(); -} - -int32_t ObstacleInfo::getIconIndex() const -{ - return iconIndex; -} - -std::string ObstacleInfo::getJsonKey() const -{ - return modScope + ':' + identifier; -} - -std::string ObstacleInfo::getModScope() const -{ - return modScope; -} - -std::string ObstacleInfo::getNameTranslated() const -{ - return identifier; -} - -std::string ObstacleInfo::getNameTextID() const -{ - return identifier; // TODO? -} - -void ObstacleInfo::registerIcons(const IconRegistar & cb) const -{ -} - -Obstacle ObstacleInfo::getId() const -{ - return obstacle; -} - -BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const -{ - if(isAbsoluteObstacle) - { - assert(!hex.isValid()); - return BattleHexArray(blockedTiles); - } - - BattleHexArray ret; - for(int offset : blockedTiles) - { - BattleHex toBlock = hex + offset; - if((hex.getY() & 1) && !(toBlock.getY() & 1)) - toBlock += BattleHex::LEFT; - - if(!toBlock.isValid()) - logGlobal->error("Misplaced obstacle!"); - else - ret.insert(toBlock); - } - - return ret; -} - -bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const -{ - const auto * bgInfo = battlefield.getInfo(); - - if(bgInfo->isSpecial) - return vstd::contains(allowedSpecialBfields, bgInfo->identifier); - - return vstd::contains(allowedTerrains, terrainType); -} - -std::shared_ptr ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) -{ - assert(identifier.find(':') == std::string::npos); - - auto info = std::make_shared(Obstacle(index), identifier); - - info->modScope = scope; - info->animation = AnimationPath::fromJson(json["animation"]); - info->width = json["width"].Integer(); - info->height = json["height"].Integer(); - for(const auto & t : json["allowedTerrains"].Vector()) - { - VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){ - info->allowedTerrains.emplace_back(identifier); - }); - } - for(const auto & t : json["specialBattlefields"].Vector()) - - info->allowedSpecialBfields.emplace_back(t.String()); - info->blockedTiles = json["blockedTiles"].convertTo>(); - info->isAbsoluteObstacle = json["absolute"].Bool(); - info->isForegroundObstacle = json["foreground"].Bool(); - - return info; -} - -std::vector ObstacleHandler::loadLegacyData() -{ - return {}; -} - -const std::vector & ObstacleHandler::getTypeNames() const -{ - static const std::vector types = { "obstacle" }; - return types; -} - -VCMI_LIB_NAMESPACE_END +/* + * ObstacleHandler.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "ObstacleHandler.h" +#include "BattleFieldHandler.h" +#include "json/JsonNode.h" +#include "modding/IdentifierStorage.h" +#include "VCMI_Lib.h" + +VCMI_LIB_NAMESPACE_BEGIN + +int32_t ObstacleInfo::getIndex() const +{ + return obstacle.getNum(); +} + +int32_t ObstacleInfo::getIconIndex() const +{ + return iconIndex; +} + +std::string ObstacleInfo::getJsonKey() const +{ + return modScope + ':' + identifier; +} + +std::string ObstacleInfo::getModScope() const +{ + return modScope; +} + +std::string ObstacleInfo::getNameTranslated() const +{ + return identifier; +} + +std::string ObstacleInfo::getNameTextID() const +{ + return identifier; // TODO? +} + +void ObstacleInfo::registerIcons(const IconRegistar & cb) const +{ +} + +Obstacle ObstacleInfo::getId() const +{ + return obstacle; +} + +BattleHexArray ObstacleInfo::getBlocked(BattleHex hex) const +{ + if(isAbsoluteObstacle) + { + assert(!hex.isValid()); + return BattleHexArray(blockedTiles); + } + + BattleHexArray ret; + for(int offset : blockedTiles) + { + BattleHex toBlock = hex + offset; + if((hex.getY() & 1) && !(toBlock.getY() & 1)) + toBlock += BattleHex::LEFT; + + if(!toBlock.isValid()) + logGlobal->error("Misplaced obstacle!"); + else + ret.insert(toBlock); + } + + return ret; +} + +bool ObstacleInfo::isAppropriate(const TerrainId terrainType, const BattleField & battlefield) const +{ + const auto * bgInfo = battlefield.getInfo(); + + if(bgInfo->isSpecial) + return vstd::contains(allowedSpecialBfields, bgInfo->identifier); + + return vstd::contains(allowedTerrains, terrainType); +} + +std::shared_ptr ObstacleHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & identifier, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + + auto info = std::make_shared(Obstacle(index), identifier); + + info->modScope = scope; + info->animation = AnimationPath::fromJson(json["animation"]); + info->width = json["width"].Integer(); + info->height = json["height"].Integer(); + for(const auto & t : json["allowedTerrains"].Vector()) + { + VLC->identifiers()->requestIdentifier("terrain", t, [info](int32_t identifier){ + info->allowedTerrains.emplace_back(identifier); + }); + } + for(const auto & t : json["specialBattlefields"].Vector()) + + info->allowedSpecialBfields.emplace_back(t.String()); + info->blockedTiles = json["blockedTiles"].convertTo>(); + info->isAbsoluteObstacle = json["absolute"].Bool(); + info->isForegroundObstacle = json["foreground"].Bool(); + + return info; +} + +std::vector ObstacleHandler::loadLegacyData() +{ + return {}; +} + +const std::vector & ObstacleHandler::getTypeNames() const +{ + static const std::vector types = { "obstacle" }; + return types; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/ObstacleHandler.h b/lib/ObstacleHandler.h index 7c81127a4..e80eb8dd6 100644 --- a/lib/ObstacleHandler.h +++ b/lib/ObstacleHandler.h @@ -1,79 +1,79 @@ -/* - * ObstacleHandler.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include -#include -#include "GameConstants.h" -#include "IHandlerBase.h" -#include "battle/BattleHexArray.h" -#include "filesystem/ResourcePath.h" - -VCMI_LIB_NAMESPACE_BEGIN - -class DLL_LINKAGE ObstacleInfo : public EntityT -{ -public: - ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false) - {} - - ObstacleInfo(Obstacle obstacle, std::string identifier) - : obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false) - { - } - - Obstacle obstacle; - si32 iconIndex; - std::string modScope; - std::string identifier; - AudioPath appearSound; - AnimationPath appearAnimation; - AnimationPath animation; - std::vector allowedTerrains; - std::vector allowedSpecialBfields; - - bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same - bool isForegroundObstacle; - si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) - si32 height; - std::vector blockedTiles; //offsets relative to obstacle position (that is its left bottom corner) - - int32_t getIndex() const override; - int32_t getIconIndex() const override; - std::string getJsonKey() const override; - std::string getModScope() const override; - std::string getNameTranslated() const override; - std::string getNameTextID() const override; - void registerIcons(const IconRegistar & cb) const override; - Obstacle getId() const override; - - BattleHexArray getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' - - bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const; -}; - -class DLL_LINKAGE ObstacleService : public EntityServiceT -{ -public: -}; - -class ObstacleHandler: public CHandlerBase -{ -public: - std::shared_ptr loadFromJson(const std::string & scope, - const JsonNode & json, - const std::string & identifier, - size_t index) override; - - const std::vector & getTypeNames() const override; - std::vector loadLegacyData() override; -}; - -VCMI_LIB_NAMESPACE_END +/* + * ObstacleHandler.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include +#include +#include "GameConstants.h" +#include "IHandlerBase.h" +#include "battle/BattleHexArray.h" +#include "filesystem/ResourcePath.h" + +VCMI_LIB_NAMESPACE_BEGIN + +class DLL_LINKAGE ObstacleInfo : public EntityT +{ +public: + ObstacleInfo(): obstacle(-1), width(0), height(0), isAbsoluteObstacle(false), iconIndex(0), isForegroundObstacle(false) + {} + + ObstacleInfo(Obstacle obstacle, std::string identifier) + : obstacle(obstacle), identifier(identifier), iconIndex(obstacle.getNum()), width(0), height(0), isAbsoluteObstacle(false), isForegroundObstacle(false) + { + } + + Obstacle obstacle; + si32 iconIndex; + std::string modScope; + std::string identifier; + AudioPath appearSound; + AnimationPath appearAnimation; + AnimationPath animation; + std::vector allowedTerrains; + std::vector allowedSpecialBfields; + + bool isAbsoluteObstacle; //there may only one such obstacle in battle and its position is always the same + bool isForegroundObstacle; + si32 width; //how much space to the right and up is needed to place obstacle (affects only placement algorithm) + si32 height; + std::vector blockedTiles; //offsets relative to obstacle position (that is its left bottom corner) + + int32_t getIndex() const override; + int32_t getIconIndex() const override; + std::string getJsonKey() const override; + std::string getModScope() const override; + std::string getNameTranslated() const override; + std::string getNameTextID() const override; + void registerIcons(const IconRegistar & cb) const override; + Obstacle getId() const override; + + BattleHexArray getBlocked(BattleHex hex) const; //returns vector of hexes blocked by obstacle when it's placed on hex 'hex' + + bool isAppropriate(const TerrainId terrainType, const BattleField & specialBattlefield) const; +}; + +class DLL_LINKAGE ObstacleService : public EntityServiceT +{ +public: +}; + +class ObstacleHandler: public CHandlerBase +{ +public: + std::shared_ptr loadFromJson(const std::string & scope, + const JsonNode & json, + const std::string & identifier, + size_t index) override; + + const std::vector & getTypeNames() const override; + std::vector loadLegacyData() override; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.cpp b/lib/battle/BattleHex.cpp index 197a0451c..7e21c4937 100644 --- a/lib/battle/BattleHex.cpp +++ b/lib/battle/BattleHex.cpp @@ -1,138 +1,138 @@ -/* - * BattleHex.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" -#include "BattleHex.h" - -VCMI_LIB_NAMESPACE_BEGIN - -BattleHex::BattleHex() : hex(INVALID) {} - -BattleHex::BattleHex(si16 _hex) : hex(_hex) {} - -BattleHex::BattleHex(si16 x, si16 y) -{ - setXY(x, y); -} - -BattleHex::BattleHex(std::pair xy) -{ - setXY(xy); -} - -BattleHex::operator si16() const -{ - return hex; -} - -void BattleHex::setX(si16 x) -{ - setXY(x, getY()); -} - -void BattleHex::setY(si16 y) -{ - setXY(getX(), y); -} - -void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) -{ - if(hasToBeValid) - { - if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) - throw std::runtime_error("Valid hex required"); - } - - hex = x + y * GameConstants::BFIELD_WIDTH; -} - -void BattleHex::setXY(std::pair xy) -{ - setXY(xy.first, xy.second); -} - -si16 BattleHex::getX() const -{ - return hex % GameConstants::BFIELD_WIDTH; -} - -si16 BattleHex::getY() const -{ - return hex / GameConstants::BFIELD_WIDTH; -} - -std::pair BattleHex::getXY() const -{ - return std::make_pair(getX(), getY()); -} - -BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid) -{ - si16 x = getX(); - si16 y = getY(); - switch(dir) - { - case TOP_LEFT: - setXY((y%2) ? x-1 : x, y-1, hasToBeValid); - break; - case TOP_RIGHT: - setXY((y%2) ? x : x+1, y-1, hasToBeValid); - break; - case RIGHT: - setXY(x+1, y, hasToBeValid); - break; - case BOTTOM_RIGHT: - setXY((y%2) ? x : x+1, y+1, hasToBeValid); - break; - case BOTTOM_LEFT: - setXY((y%2) ? x-1 : x, y+1, hasToBeValid); - break; - case LEFT: - setXY(x-1, y, hasToBeValid); - break; - case NONE: - break; - default: - throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); - break; - } - return *this; -} - -BattleHex & BattleHex::operator+=(BattleHex::EDir dir) -{ - return moveInDirection(dir); -} - -BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const -{ - BattleHex result(hex); - result.moveInDirection(dir, hasToBeValid); - return result; -} - -BattleHex BattleHex::operator+(BattleHex::EDir dir) const -{ - return cloneInDirection(dir); -} - -BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) -{ - for(auto dir : hexagonalDirections()) - if(hex2 == hex1.cloneInDirection(dir, false)) - return dir; - return NONE; -} - -std::ostream & operator<<(std::ostream & os, const BattleHex & hex) -{ - return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); -} - -VCMI_LIB_NAMESPACE_END +/* + * BattleHex.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "BattleHex.h" + +VCMI_LIB_NAMESPACE_BEGIN + +BattleHex::BattleHex() : hex(INVALID) {} + +BattleHex::BattleHex(si16 _hex) : hex(_hex) {} + +BattleHex::BattleHex(si16 x, si16 y) +{ + setXY(x, y); +} + +BattleHex::BattleHex(std::pair xy) +{ + setXY(xy); +} + +BattleHex::operator si16() const +{ + return hex; +} + +void BattleHex::setX(si16 x) +{ + setXY(x, getY()); +} + +void BattleHex::setY(si16 y) +{ + setXY(getX(), y); +} + +void BattleHex::setXY(si16 x, si16 y, bool hasToBeValid) +{ + if(hasToBeValid) + { + if(x < 0 || x >= GameConstants::BFIELD_WIDTH || y < 0 || y >= GameConstants::BFIELD_HEIGHT) + throw std::runtime_error("Valid hex required"); + } + + hex = x + y * GameConstants::BFIELD_WIDTH; +} + +void BattleHex::setXY(std::pair xy) +{ + setXY(xy.first, xy.second); +} + +si16 BattleHex::getX() const +{ + return hex % GameConstants::BFIELD_WIDTH; +} + +si16 BattleHex::getY() const +{ + return hex / GameConstants::BFIELD_WIDTH; +} + +std::pair BattleHex::getXY() const +{ + return std::make_pair(getX(), getY()); +} + +BattleHex & BattleHex::moveInDirection(EDir dir, bool hasToBeValid) +{ + si16 x = getX(); + si16 y = getY(); + switch(dir) + { + case TOP_LEFT: + setXY((y%2) ? x-1 : x, y-1, hasToBeValid); + break; + case TOP_RIGHT: + setXY((y%2) ? x : x+1, y-1, hasToBeValid); + break; + case RIGHT: + setXY(x+1, y, hasToBeValid); + break; + case BOTTOM_RIGHT: + setXY((y%2) ? x : x+1, y+1, hasToBeValid); + break; + case BOTTOM_LEFT: + setXY((y%2) ? x-1 : x, y+1, hasToBeValid); + break; + case LEFT: + setXY(x-1, y, hasToBeValid); + break; + case NONE: + break; + default: + throw std::runtime_error("Disaster: wrong direction in BattleHex::operator+=!\n"); + break; + } + return *this; +} + +BattleHex & BattleHex::operator+=(BattleHex::EDir dir) +{ + return moveInDirection(dir); +} + +BattleHex BattleHex::cloneInDirection(BattleHex::EDir dir, bool hasToBeValid) const +{ + BattleHex result(hex); + result.moveInDirection(dir, hasToBeValid); + return result; +} + +BattleHex BattleHex::operator+(BattleHex::EDir dir) const +{ + return cloneInDirection(dir); +} + +BattleHex::EDir BattleHex::mutualPosition(BattleHex hex1, BattleHex hex2) +{ + for(auto dir : hexagonalDirections()) + if(hex2 == hex1.cloneInDirection(dir, false)) + return dir; + return NONE; +} + +std::ostream & operator<<(std::ostream & os, const BattleHex & hex) +{ + return os << boost::str(boost::format("{BattleHex: x '%d', y '%d', hex '%d'}") % hex.getX() % hex.getY() % hex.hex); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/BattleHex.h b/lib/battle/BattleHex.h index e86a5caf1..4751d7515 100644 --- a/lib/battle/BattleHex.h +++ b/lib/battle/BattleHex.h @@ -1,127 +1,127 @@ -/* - * BattleHex.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "BattleSide.h" - -VCMI_LIB_NAMESPACE_BEGIN - -//TODO: change to enum class - -namespace GameConstants -{ - const int BFIELD_WIDTH = 17; - const int BFIELD_HEIGHT = 11; - const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; -} - -// for battle stacks' positions -struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design -{ - // helpers for siege - static constexpr si16 CASTLE_CENTRAL_TOWER = -2; - static constexpr si16 CASTLE_BOTTOM_TOWER = -3; - static constexpr si16 CASTLE_UPPER_TOWER = -4; - - // hexes for interaction with heroes - static constexpr si16 HERO_ATTACKER = 0; - static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1; - - // helpers for rendering - static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits::min(); - static constexpr si16 HEX_AFTER_ALL = std::numeric_limits::max(); - - static constexpr si16 DESTRUCTIBLE_WALL_1 = 29; - static constexpr si16 DESTRUCTIBLE_WALL_2 = 78; - static constexpr si16 DESTRUCTIBLE_WALL_3 = 130; - static constexpr si16 DESTRUCTIBLE_WALL_4 = 182; - static constexpr si16 GATE_BRIDGE = 94; - static constexpr si16 GATE_OUTER = 95; - static constexpr si16 GATE_INNER = 96; - - si16 hex; - static constexpr si16 INVALID = -1; - enum EDir - { - NONE = -1, - - TOP_LEFT, - TOP_RIGHT, - RIGHT, - BOTTOM_RIGHT, - BOTTOM_LEFT, - LEFT, - - //Note: unused by BattleHex class, used by other code - TOP, - BOTTOM - }; - - BattleHex(); - BattleHex(si16 _hex); - BattleHex(si16 x, si16 y); - BattleHex(std::pair xy); - operator si16() const; - inline bool isValid() const - { - return hex >= 0 && hex < GameConstants::BFIELD_SIZE; - } - - bool isAvailable() const //valid position not in first or last column - { - return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; - } - - void setX(si16 x); - void setY(si16 y); - void setXY(si16 x, si16 y, bool hasToBeValid = true); - void setXY(std::pair xy); - si16 getX() const; - si16 getY() const; - std::pair getXY() const; - BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); - BattleHex& operator+=(EDir dir); - BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const; - BattleHex operator+(EDir dir) const; - - static EDir mutualPosition(BattleHex hex1, BattleHex hex2); - static uint8_t getDistance(BattleHex hex1, BattleHex hex2) - { - int y1 = hex1.getY(); - int y2 = hex2.getY(); - - // FIXME: why there was * 0.5 instead of / 2? - int x1 = static_cast(hex1.getX() + y1 / 2); - int x2 = static_cast(hex2.getX() + y2 / 2); - - int xDst = x2 - x1; - int yDst = y2 - y1; - - if((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0)) - return std::max(std::abs(xDst), std::abs(yDst)); - - return std::abs(xDst) + std::abs(yDst); - } - - template - void serialize(Handler &h) - { - h & hex; - } - - //Constexpr defined array with all directions used in battle - static constexpr auto hexagonalDirections() { - return std::array{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; - } -}; - -DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); - -VCMI_LIB_NAMESPACE_END +/* + * BattleHex.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "BattleSide.h" + +VCMI_LIB_NAMESPACE_BEGIN + +//TODO: change to enum class + +namespace GameConstants +{ + const int BFIELD_WIDTH = 17; + const int BFIELD_HEIGHT = 11; + const int BFIELD_SIZE = BFIELD_WIDTH * BFIELD_HEIGHT; +} + +// for battle stacks' positions +struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class for better code design +{ + // helpers for siege + static constexpr si16 CASTLE_CENTRAL_TOWER = -2; + static constexpr si16 CASTLE_BOTTOM_TOWER = -3; + static constexpr si16 CASTLE_UPPER_TOWER = -4; + + // hexes for interaction with heroes + static constexpr si16 HERO_ATTACKER = 0; + static constexpr si16 HERO_DEFENDER = GameConstants::BFIELD_WIDTH - 1; + + // helpers for rendering + static constexpr si16 HEX_BEFORE_ALL = std::numeric_limits::min(); + static constexpr si16 HEX_AFTER_ALL = std::numeric_limits::max(); + + static constexpr si16 DESTRUCTIBLE_WALL_1 = 29; + static constexpr si16 DESTRUCTIBLE_WALL_2 = 78; + static constexpr si16 DESTRUCTIBLE_WALL_3 = 130; + static constexpr si16 DESTRUCTIBLE_WALL_4 = 182; + static constexpr si16 GATE_BRIDGE = 94; + static constexpr si16 GATE_OUTER = 95; + static constexpr si16 GATE_INNER = 96; + + si16 hex; + static constexpr si16 INVALID = -1; + enum EDir + { + NONE = -1, + + TOP_LEFT, + TOP_RIGHT, + RIGHT, + BOTTOM_RIGHT, + BOTTOM_LEFT, + LEFT, + + //Note: unused by BattleHex class, used by other code + TOP, + BOTTOM + }; + + BattleHex(); + BattleHex(si16 _hex); + BattleHex(si16 x, si16 y); + BattleHex(std::pair xy); + operator si16() const; + inline bool isValid() const + { + return hex >= 0 && hex < GameConstants::BFIELD_SIZE; + } + + bool isAvailable() const //valid position not in first or last column + { + return isValid() && getX() > 0 && getX() < GameConstants::BFIELD_WIDTH - 1; + } + + void setX(si16 x); + void setY(si16 y); + void setXY(si16 x, si16 y, bool hasToBeValid = true); + void setXY(std::pair xy); + si16 getX() const; + si16 getY() const; + std::pair getXY() const; + BattleHex& moveInDirection(EDir dir, bool hasToBeValid = true); + BattleHex& operator+=(EDir dir); + BattleHex cloneInDirection(EDir dir, bool hasToBeValid = true) const; + BattleHex operator+(EDir dir) const; + + static EDir mutualPosition(BattleHex hex1, BattleHex hex2); + static uint8_t getDistance(BattleHex hex1, BattleHex hex2) + { + int y1 = hex1.getY(); + int y2 = hex2.getY(); + + // FIXME: why there was * 0.5 instead of / 2? + int x1 = static_cast(hex1.getX() + y1 / 2); + int x2 = static_cast(hex2.getX() + y2 / 2); + + int xDst = x2 - x1; + int yDst = y2 - y1; + + if((xDst >= 0 && yDst >= 0) || (xDst < 0 && yDst < 0)) + return std::max(std::abs(xDst), std::abs(yDst)); + + return std::abs(xDst) + std::abs(yDst); + } + + template + void serialize(Handler &h) + { + h & hex; + } + + //Constexpr defined array with all directions used in battle + static constexpr auto hexagonalDirections() { + return std::array{BattleHex::TOP_LEFT, BattleHex::TOP_RIGHT, BattleHex::RIGHT, BattleHex::BOTTOM_RIGHT, BattleHex::BOTTOM_LEFT, BattleHex::LEFT}; + } +}; + +DLL_EXPORT std::ostream & operator<<(std::ostream & os, const BattleHex & hex); + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/Destination.h b/lib/battle/Destination.h index 4d321589d..25c7d9f97 100644 --- a/lib/battle/Destination.h +++ b/lib/battle/Destination.h @@ -1,43 +1,43 @@ -/* - * Destination.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "BattleHexArray.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace battle -{ - -class Unit; - -class DLL_LINKAGE Destination -{ -public: - Destination(); - ~Destination() = default; - explicit Destination(const Unit * destination); - explicit Destination(const BattleHex & destination); - explicit Destination(const Unit * destination, const BattleHex & exactHex); - - Destination(const Destination & other) = default; - - Destination & operator=(const Destination & other) = default; - - const Unit * unitValue; - BattleHex hexValue; -}; - -using Target = std::vector; - -} - -VCMI_LIB_NAMESPACE_END +/* + * Destination.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "BattleHexArray.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace battle +{ + +class Unit; + +class DLL_LINKAGE Destination +{ +public: + Destination(); + ~Destination() = default; + explicit Destination(const Unit * destination); + explicit Destination(const BattleHex & destination); + explicit Destination(const Unit * destination, const BattleHex & exactHex); + + Destination(const Destination & other) = default; + + Destination & operator=(const Destination & other) = default; + + const Unit * unitValue; + BattleHex hexValue; +}; + +using Target = std::vector; + +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/IBattleInfoCallback.h b/lib/battle/IBattleInfoCallback.h index c858e6e9f..546eb9a7e 100644 --- a/lib/battle/IBattleInfoCallback.h +++ b/lib/battle/IBattleInfoCallback.h @@ -1,89 +1,89 @@ -/* - * IBattleInfoCallback.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "GameConstants.h" -#include "BattleHexArray.h" - -#include - -#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false) - -VCMI_LIB_NAMESPACE_BEGIN - -struct CObstacleInstance; -class BattleField; -class IBattleInfo; - -namespace battle -{ - class IUnitInfo; - class Unit; - using Units = std::vector; - using UnitFilter = std::function; -} - -struct DamageRange -{ - int64_t min = 0; - int64_t max = 0; -}; - -struct DamageEstimation -{ - DamageRange damage; - DamageRange kills; -}; - -#if SCRIPTING_ENABLED -namespace scripting -{ - class Pool; -} -#endif - -class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider -{ -public: -#if SCRIPTING_ENABLED - virtual scripting::Pool * getContextPool() const = 0; -#endif - virtual ~IBattleInfoCallback() = default; - - virtual const IBattleInfo * getBattle() const = 0; - virtual std::optional getPlayerID() const = 0; - - virtual TerrainId battleTerrainType() const = 0; - virtual BattleField battleGetBattlefieldType() const = 0; - - ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw - virtual std::optional battleIsFinished() const = 0; - - virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase - virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?) - - virtual uint32_t battleNextUnitId() const = 0; - - virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0; - - virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0; - virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0; - - virtual const battle::Unit * battleActiveUnit() const = 0; - - //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) - virtual std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0; - virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const = 0; -}; - - - -VCMI_LIB_NAMESPACE_END +/* + * IBattleInfoCallback.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "GameConstants.h" +#include "BattleHexArray.h" + +#include + +#define RETURN_IF_NOT_BATTLE(...) do { if(!duringBattle()) {logGlobal->error("%s called when no battle!", __FUNCTION__); return __VA_ARGS__; } } while (false) + +VCMI_LIB_NAMESPACE_BEGIN + +struct CObstacleInstance; +class BattleField; +class IBattleInfo; + +namespace battle +{ + class IUnitInfo; + class Unit; + using Units = std::vector; + using UnitFilter = std::function; +} + +struct DamageRange +{ + int64_t min = 0; + int64_t max = 0; +}; + +struct DamageEstimation +{ + DamageRange damage; + DamageRange kills; +}; + +#if SCRIPTING_ENABLED +namespace scripting +{ + class Pool; +} +#endif + +class DLL_LINKAGE IBattleInfoCallback : public IConstBonusProvider +{ +public: +#if SCRIPTING_ENABLED + virtual scripting::Pool * getContextPool() const = 0; +#endif + virtual ~IBattleInfoCallback() = default; + + virtual const IBattleInfo * getBattle() const = 0; + virtual std::optional getPlayerID() const = 0; + + virtual TerrainId battleTerrainType() const = 0; + virtual BattleField battleGetBattlefieldType() const = 0; + + ///return none if battle is ongoing; otherwise the victorious side (0/1) or 2 if it is a draw + virtual std::optional battleIsFinished() const = 0; + + virtual si8 battleTacticDist() const = 0; //returns tactic distance in current tactics phase; 0 if not in tactics phase + virtual BattleSide battleGetTacticsSide() const = 0; //returns which side is in tactics phase, undefined if none (?) + + virtual uint32_t battleNextUnitId() const = 0; + + virtual battle::Units battleGetUnitsIf(const battle::UnitFilter & predicate) const = 0; + + virtual const battle::Unit * battleGetUnitByID(uint32_t ID) const = 0; + virtual const battle::Unit * battleGetUnitByPos(BattleHex pos, bool onlyAlive = true) const = 0; + + virtual const battle::Unit * battleActiveUnit() const = 0; + + //blocking obstacles makes tile inaccessible, others cause special effects (like Land Mines, Moat, Quicksands) + virtual std::vector> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const = 0; + virtual std::vector> getAllAffectedObstaclesByStack(const battle::Unit * unit, const BattleHexArray & passed) const = 0; +}; + + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/battle/ReachabilityInfo.cpp b/lib/battle/ReachabilityInfo.cpp index 826fa6626..e00393d4a 100644 --- a/lib/battle/ReachabilityInfo.cpp +++ b/lib/battle/ReachabilityInfo.cpp @@ -1,89 +1,89 @@ -/* - * ReachabilityInfo.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "ReachabilityInfo.h" -#include "Unit.h" - -VCMI_LIB_NAMESPACE_BEGIN - -ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition): - perspective(static_cast(Stack->unitSide())), - startPosition(StartPosition), - doubleWide(Stack->doubleWide()), - side(Stack->unitSide()), - flying(Stack->hasBonusOfType(BonusType::FLYING)) -{ - knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); -} - -ReachabilityInfo::ReachabilityInfo() -{ - distances.fill(INFINITE_DIST); - predecessors.fill(BattleHex::INVALID); -} - -bool ReachabilityInfo::isReachable(BattleHex hex) const -{ - return distances[hex] < INFINITE_DIST; -} - -uint32_t ReachabilityInfo::distToNearestNeighbour( - const BattleHexArray & targetHexes, - BattleHex * chosenHex) const -{ - uint32_t ret = 1000000; - - for(auto targetHex : targetHexes) - { - for(auto & n : BattleHexArray::neighbouringTilesCache[targetHex]) - { - if(distances[n] < ret) - { - ret = distances[n]; - if(chosenHex) - *chosenHex = n; - } - } - } - - return ret; -} - -uint32_t ReachabilityInfo::distToNearestNeighbour( - const battle::Unit * attacker, - const battle::Unit * defender, - BattleHex * chosenHex) const -{ - auto attackableHexes = defender->getHexes(); - - if(attacker->doubleWide()) - { - if(defender->doubleWide()) - { - // It can be back to back attack o==o or head to head =oo=. - // In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles - attackableHexes.merge(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); - } - else - { - attackableHexes.merge(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); - } - } - - vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool - { - return h.getY() != defender->getPosition().getY() || !h.isAvailable(); - }); - - return distToNearestNeighbour(attackableHexes, chosenHex); -} - -VCMI_LIB_NAMESPACE_END +/* + * ReachabilityInfo.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "ReachabilityInfo.h" +#include "Unit.h" + +VCMI_LIB_NAMESPACE_BEGIN + +ReachabilityInfo::Parameters::Parameters(const battle::Unit * Stack, BattleHex StartPosition): + perspective(static_cast(Stack->unitSide())), + startPosition(StartPosition), + doubleWide(Stack->doubleWide()), + side(Stack->unitSide()), + flying(Stack->hasBonusOfType(BonusType::FLYING)) +{ + knownAccessible = battle::Unit::getHexes(startPosition, doubleWide, side); +} + +ReachabilityInfo::ReachabilityInfo() +{ + distances.fill(INFINITE_DIST); + predecessors.fill(BattleHex::INVALID); +} + +bool ReachabilityInfo::isReachable(BattleHex hex) const +{ + return distances[hex] < INFINITE_DIST; +} + +uint32_t ReachabilityInfo::distToNearestNeighbour( + const BattleHexArray & targetHexes, + BattleHex * chosenHex) const +{ + uint32_t ret = 1000000; + + for(auto targetHex : targetHexes) + { + for(auto & n : BattleHexArray::neighbouringTilesCache[targetHex]) + { + if(distances[n] < ret) + { + ret = distances[n]; + if(chosenHex) + *chosenHex = n; + } + } + } + + return ret; +} + +uint32_t ReachabilityInfo::distToNearestNeighbour( + const battle::Unit * attacker, + const battle::Unit * defender, + BattleHex * chosenHex) const +{ + auto attackableHexes = defender->getHexes(); + + if(attacker->doubleWide()) + { + if(defender->doubleWide()) + { + // It can be back to back attack o==o or head to head =oo=. + // In case of back-to-back the distance between heads (unit positions) may be up to 3 tiles + attackableHexes.merge(battle::Unit::getHexes(defender->occupiedHex(), true, defender->unitSide())); + } + else + { + attackableHexes.merge(battle::Unit::getHexes(defender->getPosition(), true, defender->unitSide())); + } + } + + vstd::erase_if(attackableHexes, [defender](BattleHex h) -> bool + { + return h.getY() != defender->getPosition().getY() || !h.isAvailable(); + }); + + return distToNearestNeighbour(attackableHexes, chosenHex); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/bonuses/Limiters.cpp b/lib/bonuses/Limiters.cpp index f84b2de48..6243ec541 100644 --- a/lib/bonuses/Limiters.cpp +++ b/lib/bonuses/Limiters.cpp @@ -220,8 +220,8 @@ ILimiter::EDecision UnitOnHexLimiter::limit(const BonusLimitationContext &contex return ILimiter::EDecision::DISCARD; auto accept = false; - for (const auto & hex : stack->getHexes()) - accept |= !!applicableHexes.count(hex); + for (auto hex : stack->getHexes()) + accept |= applicableHexes.contains(hex); return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD; } @@ -236,7 +236,7 @@ JsonNode UnitOnHexLimiter::toJsonNode() const JsonNode root; root["type"].String() = "UNIT_ON_HEXES"; - for(const auto & hex : applicableHexes) + for(auto hex : applicableHexes) root["parameters"].Vector().emplace_back(hex); return root; diff --git a/lib/pathfinder/CGPathNode.h b/lib/pathfinder/CGPathNode.h index fd9f80d3c..2598633cc 100644 --- a/lib/pathfinder/CGPathNode.h +++ b/lib/pathfinder/CGPathNode.h @@ -1,243 +1,243 @@ -/* - * CGPathNode.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../GameConstants.h" -#include "../int3.h" - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -class CGHeroInstance; -class CGObjectInstance; -class CGameState; -class CPathfinderHelper; -struct TerrainTile; - -template -struct DLL_LINKAGE NodeComparer -{ - STRONG_INLINE - bool operator()(const N * lhs, const N * rhs) const - { - return lhs->getCost() > rhs->getCost(); - } -}; - -enum class EPathAccessibility : ui8 -{ - NOT_SET, - ACCESSIBLE, //tile can be entered and passed - VISITABLE, //tile can be entered as the last tile in path - GUARDED, //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any) - BLOCKVIS, //visitable from neighbouring tile but not passable - FLYABLE, //can only be accessed in air layer - BLOCKED //tile can be neither entered nor visited -}; - -enum class EPathNodeAction : ui8 -{ - UNKNOWN, - EMBARK, - DISEMBARK, - NORMAL, - BATTLE, - VISIT, - BLOCKING_VISIT, - TELEPORT_NORMAL, - TELEPORT_BLOCKING_VISIT, - TELEPORT_BATTLE -}; - -struct DLL_LINKAGE CGPathNode -{ - using TFibHeap = boost::heap::fibonacci_heap>>; - using ELayer = EPathfindingLayer; - - TFibHeap::handle_type pqHandle; - TFibHeap * pq; - CGPathNode * theNodeBefore; - - int3 coord; //coordinates - ELayer layer; - - float cost; //total cost of the path to this tile measured in turns with fractions - int moveRemains; //remaining movement points after hero reaches the tile - ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn - EPathAccessibility accessible; - EPathNodeAction action; - bool locked; - - CGPathNode() - : coord(-1), - layer(ELayer::WRONG), - pqHandle(nullptr) - { - reset(); - } - - STRONG_INLINE - void reset() - { - locked = false; - accessible = EPathAccessibility::NOT_SET; - moveRemains = 0; - cost = std::numeric_limits::max(); - turns = 255; - theNodeBefore = nullptr; - pq = nullptr; - action = EPathNodeAction::UNKNOWN; - } - - STRONG_INLINE - bool inPQ() const - { - return pq != nullptr; - } - - STRONG_INLINE - float getCost() const - { - return cost; - } - - STRONG_INLINE - void setCost(float value) - { - if(vstd::isAlmostEqual(value, cost)) - return; - - bool getUpNode = value < cost; - cost = value; - // If the node is in the heap, update the heap. - if(inPQ()) - { - if(getUpNode) - { - pq->increase(this->pqHandle); - } - else - { - pq->decrease(this->pqHandle); - } - } - } - - STRONG_INLINE - void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible) - { - if(layer == ELayer::WRONG) - { - coord = Coord; - layer = Layer; - } - else - { - reset(); - } - - accessible = Accessible; - } - - STRONG_INLINE - bool reachable() const - { - return turns < 255; - } - - bool isTeleportAction() const - { - if (action != EPathNodeAction::TELEPORT_NORMAL && - action != EPathNodeAction::TELEPORT_BLOCKING_VISIT && - action != EPathNodeAction::TELEPORT_BATTLE) - { - return false; - } - - return true; - } -}; - -struct DLL_LINKAGE CGPath -{ - std::vector nodes; //just get node by node - - /// Starting position of path, matches location of hero - const CGPathNode & currNode() const; - /// First node in path, this is where hero will move next - const CGPathNode & nextNode() const; - /// Last node in path, this is what hero wants to reach in the end - const CGPathNode & lastNode() const; - - int3 startPos() const; // start point - int3 endPos() const; //destination point -}; - -struct DLL_LINKAGE CPathsInfo -{ - using ELayer = EPathfindingLayer; - - const CGHeroInstance * hero; - int3 hpos; - int3 sizes; - boost::multi_array nodes; //[layer][level][w][h] - - CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); - ~CPathsInfo(); - const CGPathNode * getPathInfo(const int3 & tile) const; - bool getPath(CGPath & out, const int3 & dst) const; - const CGPathNode * getNode(const int3 & coord) const; - - STRONG_INLINE - CGPathNode * getNode(const int3 & coord, const ELayer layer) - { - return &nodes[layer.getNum()][coord.z][coord.x][coord.y]; - } -}; - -struct DLL_LINKAGE PathNodeInfo -{ - CGPathNode * node; - const CGObjectInstance * nodeObject; - const CGHeroInstance * nodeHero; - const TerrainTile * tile; - int3 coord; - bool guarded; - PlayerRelations objectRelations; - PlayerRelations heroRelations; - bool isInitialPosition; - - PathNodeInfo(); - - virtual void setNode(CGameState * gs, CGPathNode * n); - - void updateInfo(CPathfinderHelper * hlp, CGameState * gs); - - bool isNodeObjectVisitable() const; -}; - -struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo -{ - EPathNodeAction action; - int turn; - int movementLeft; - float cost; //same as CGPathNode::cost - bool blocked; - bool isGuardianTile; - - CDestinationNodeInfo(); - - void setNode(CGameState * gs, CGPathNode * n) override; - - virtual bool isBetterWay() const; -}; - -VCMI_LIB_NAMESPACE_END +/* + * CGPathNode.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../GameConstants.h" +#include "../int3.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +class CGHeroInstance; +class CGObjectInstance; +class CGameState; +class CPathfinderHelper; +struct TerrainTile; + +template +struct DLL_LINKAGE NodeComparer +{ + STRONG_INLINE + bool operator()(const N * lhs, const N * rhs) const + { + return lhs->getCost() > rhs->getCost(); + } +}; + +enum class EPathAccessibility : ui8 +{ + NOT_SET, + ACCESSIBLE, //tile can be entered and passed + VISITABLE, //tile can be entered as the last tile in path + GUARDED, //tile can be entered, but is in zone of control of nearby monster (may also contain visitable object, if any) + BLOCKVIS, //visitable from neighboring tile but not passable + FLYABLE, //can only be accessed in air layer + BLOCKED //tile can be neither entered nor visited +}; + +enum class EPathNodeAction : ui8 +{ + UNKNOWN, + EMBARK, + DISEMBARK, + NORMAL, + BATTLE, + VISIT, + BLOCKING_VISIT, + TELEPORT_NORMAL, + TELEPORT_BLOCKING_VISIT, + TELEPORT_BATTLE +}; + +struct DLL_LINKAGE CGPathNode +{ + using TFibHeap = boost::heap::fibonacci_heap>>; + using ELayer = EPathfindingLayer; + + TFibHeap::handle_type pqHandle; + TFibHeap * pq; + CGPathNode * theNodeBefore; + + int3 coord; //coordinates + ELayer layer; + + float cost; //total cost of the path to this tile measured in turns with fractions + int moveRemains; //remaining movement points after hero reaches the tile + ui8 turns; //how many turns we have to wait before reaching the tile - 0 means current turn + EPathAccessibility accessible; + EPathNodeAction action; + bool locked; + + CGPathNode() + : coord(-1), + layer(ELayer::WRONG), + pqHandle(nullptr) + { + reset(); + } + + STRONG_INLINE + void reset() + { + locked = false; + accessible = EPathAccessibility::NOT_SET; + moveRemains = 0; + cost = std::numeric_limits::max(); + turns = 255; + theNodeBefore = nullptr; + pq = nullptr; + action = EPathNodeAction::UNKNOWN; + } + + STRONG_INLINE + bool inPQ() const + { + return pq != nullptr; + } + + STRONG_INLINE + float getCost() const + { + return cost; + } + + STRONG_INLINE + void setCost(float value) + { + if(vstd::isAlmostEqual(value, cost)) + return; + + bool getUpNode = value < cost; + cost = value; + // If the node is in the heap, update the heap. + if(inPQ()) + { + if(getUpNode) + { + pq->increase(this->pqHandle); + } + else + { + pq->decrease(this->pqHandle); + } + } + } + + STRONG_INLINE + void update(const int3 & Coord, const ELayer Layer, const EPathAccessibility Accessible) + { + if(layer == ELayer::WRONG) + { + coord = Coord; + layer = Layer; + } + else + { + reset(); + } + + accessible = Accessible; + } + + STRONG_INLINE + bool reachable() const + { + return turns < 255; + } + + bool isTeleportAction() const + { + if (action != EPathNodeAction::TELEPORT_NORMAL && + action != EPathNodeAction::TELEPORT_BLOCKING_VISIT && + action != EPathNodeAction::TELEPORT_BATTLE) + { + return false; + } + + return true; + } +}; + +struct DLL_LINKAGE CGPath +{ + std::vector nodes; //just get node by node + + /// Starting position of path, matches location of hero + const CGPathNode & currNode() const; + /// First node in path, this is where hero will move next + const CGPathNode & nextNode() const; + /// Last node in path, this is what hero wants to reach in the end + const CGPathNode & lastNode() const; + + int3 startPos() const; // start point + int3 endPos() const; //destination point +}; + +struct DLL_LINKAGE CPathsInfo +{ + using ELayer = EPathfindingLayer; + + const CGHeroInstance * hero; + int3 hpos; + int3 sizes; + boost::multi_array nodes; //[layer][level][w][h] + + CPathsInfo(const int3 & Sizes, const CGHeroInstance * hero_); + ~CPathsInfo(); + const CGPathNode * getPathInfo(const int3 & tile) const; + bool getPath(CGPath & out, const int3 & dst) const; + const CGPathNode * getNode(const int3 & coord) const; + + STRONG_INLINE + CGPathNode * getNode(const int3 & coord, const ELayer layer) + { + return &nodes[layer.getNum()][coord.z][coord.x][coord.y]; + } +}; + +struct DLL_LINKAGE PathNodeInfo +{ + CGPathNode * node; + const CGObjectInstance * nodeObject; + const CGHeroInstance * nodeHero; + const TerrainTile * tile; + int3 coord; + bool guarded; + PlayerRelations objectRelations; + PlayerRelations heroRelations; + bool isInitialPosition; + + PathNodeInfo(); + + virtual void setNode(CGameState * gs, CGPathNode * n); + + void updateInfo(CPathfinderHelper * hlp, CGameState * gs); + + bool isNodeObjectVisitable() const; +}; + +struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo +{ + EPathNodeAction action; + int turn; + int movementLeft; + float cost; //same as CGPathNode::cost + bool blocked; + bool isGuardianTile; + + CDestinationNodeInfo(); + + void setNode(CGameState * gs, CGPathNode * n) override; + + virtual bool isBetterWay() const; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/CZonePlacer.cpp b/lib/rmg/CZonePlacer.cpp index 2ae643a59..29b1acfc6 100644 --- a/lib/rmg/CZonePlacer.cpp +++ b/lib/rmg/CZonePlacer.cpp @@ -1,1027 +1,1027 @@ -/* - * CZonePlacer.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "CZonePlacer.h" - -#include "../TerrainHandler.h" -#include "../entities/faction/CFaction.h" -#include "../entities/faction/CTownHandler.h" -#include "../mapping/CMap.h" -#include "../mapping/CMapEditManager.h" -#include "../VCMI_Lib.h" -#include "CMapGenOptions.h" -#include "RmgMap.h" -#include "Zone.h" -#include "Functions.h" -#include "PenroseTiling.h" - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -//#define ZONE_PLACEMENT_LOG true - -CZonePlacer::CZonePlacer(RmgMap & map) - : width(0), height(0), mapSize(0), - gravityConstant(1e-3f), - stiffnessConstant(3e-3f), - stifness(0), - stiffnessIncreaseFactor(1.03f), - bestTotalDistance(1e10), - bestTotalOverlap(1e10), - map(map) -{ -} - -int3 CZonePlacer::cords(const float3 & f) const -{ - return int3(static_cast(std::max(0.f, (f.x * map.width()) - 1)), static_cast(std::max(0.f, (f.y * map.height() - 1))), f.z); -} - -float CZonePlacer::getDistance (float distance) const -{ - return (distance ? distance * distance : 1e-6f); -} - -void CZonePlacer::findPathsBetweenZones() -{ - auto zones = map.getZones(); - - std::set> zonesToCheck; - - // Iterate through each pair of nodes in the graph - - for (const auto& zone : zones) - { - int start = zone.first; - distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 - - std::queue q; - std::map visited; - visited[start] = true; - q.push(start); - - // Perform Breadth-First Search from the starting node - while (!q.empty()) - { - int current = q.front(); - q.pop(); - - const auto& currentZone = zones.at(current); - const auto& connectedZoneIds = currentZone->getConnections(); - - for (auto & connection : connectedZoneIds) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - auto neighbour = connection.getOtherZoneId(current); - - if (current == neighbour) - { - //Do not consider self-connections - continue; - } - - if (!visited[neighbour]) - { - visited[neighbour] = true; - q.push(neighbour); - distancesBetweenZones[start][neighbour] = distancesBetweenZones[start][current] + 1; - } - } - } - } -} - -void CZonePlacer::placeOnGrid(vstd::RNG* rand) -{ - auto zones = map.getZones(); - assert(zones.size()); - - //Make sure there are at least as many grid fields as the number of zones - size_t gridSize = std::ceil(std::sqrt(zones.size())); - - typedef boost::multi_array, 2> GridType; - GridType grid(boost::extents[gridSize][gridSize]); - - TZoneVector zonesVector(zones.begin(), zones.end()); - - //Place first zone - - auto firstZone = zonesVector[0].second; - size_t x = 0; - size_t y = 0; - - auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y) - { - switch (rand->nextInt(0, 3) % 4) - { - case 0: - x = 0; - y = gridSize / 2; - break; - case 1: - x = gridSize - 1; - y = gridSize / 2; - break; - case 2: - x = gridSize / 2; - y = 0; - break; - case 3: - x = gridSize / 2; - y = gridSize - 1; - break; - } - }; - - switch (firstZone->getType()) - { - case ETemplateZoneType::PLAYER_START: - case ETemplateZoneType::CPU_START: - if (firstZone->getConnectedZoneIds().size() > 2) - { - getRandomEdge(x, y); - } - else - { - //Random corner - if (rand->nextInt(0, 1) == 1) - { - x = 0; - } - else - { - x = gridSize - 1; - } - if (rand->nextInt(0, 1) == 1) - { - y = 0; - } - else - { - y = gridSize - 1; - } - } - break; - case ETemplateZoneType::TREASURE: - if (gridSize & 1) //odd - { - x = y = (gridSize / 2); - } - else - { - //One of 4 squares in the middle - x = (gridSize / 2) - 1 + rand->nextInt(0, 1); - y = (gridSize / 2) - 1 + rand->nextInt(0, 1); - } - break; - case ETemplateZoneType::JUNCTION: - getRandomEdge(x, y); - break; - } - grid[x][y] = firstZone; - - //Ignore z placement for simplicity - - for (size_t i = 1; i < zones.size(); i++) - { - auto zone = zonesVector[i].second; - auto connectedZoneIds = zone->getConnectedZoneIds(); - - float maxDistance = -1000.0; - int3 mostDistantPlace; - - //Iterate over free positions - for (size_t freeX = 0; freeX < gridSize; ++freeX) - { - for (size_t freeY = 0; freeY < gridSize; ++freeY) - { - if (!grid[freeX][freeY]) - { - //There is free space left here - int3 potentialPos(freeX, freeY, 0); - - //Compute distance to every existing zone - - float distance = 0; - for (size_t existingX = 0; existingX < gridSize; ++existingX) - { - for (size_t existingY = 0; existingY < gridSize; ++existingY) - { - auto existingZone = grid[existingX][existingY]; - if (existingZone) - { - //There is already zone here - float localDistance = 0.0f; - - auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()]; - if (graphDistance > 1) - { - //No direct connection - localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance; - } - else - { - //Has direct connection - place as close as possible - localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0)); - } - - localDistance *= scaleForceBetweenZones(zone, existingZone); - - distance += localDistance; - } - } - } - if (distance > maxDistance) - { - maxDistance = distance; - mostDistantPlace = potentialPos; - } - } - } - } - - //Place in a free slot - grid[mostDistantPlace.x][mostDistantPlace.y] = zone; - } - - //TODO: toggle with a flag -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Initial zone grid:"); - for (size_t x = 0; x < gridSize; ++x) - { - std::string s; - for (size_t y = 0; y < gridSize; ++y) - { - if (grid[x][y]) - { - s += (boost::format("%3d ") % grid[x][y]->getId()).str(); - } - else - { - s += " -- "; - } - } - logGlobal->trace(s); - } -#endif - - //Set initial position for zones - random position in square centered around (x, y) - for (size_t x = 0; x < gridSize; ++x) - { - for (size_t y = 0; y < gridSize; ++y) - { - auto zone = grid[x][y]; - if (zone) - { - //i.e. for grid size 5 we get range (0.25 - 4.75) - auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f); - vstd::abetween(targetX, 0.5, gridSize - 0.5); - auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f); - vstd::abetween(targetY, 0.5, gridSize - 0.5); - - zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); - } - } - } -} - -float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const -{ - if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game - { - int firstPlayer = zoneA->getOwner().value(); - int secondPlayer = zoneB->getOwner().value(); - - //Players with lower indexes (especially 1 and 2) will be placed further apart - - return (1.0f + (2.0f / (firstPlayer * secondPlayer))); - } - else - { - return 1; - } -} - -void CZonePlacer::placeZones(vstd::RNG * rand) -{ - logGlobal->info("Starting zone placement"); - - width = map.getMapGenOptions().getWidth(); - height = map.getMapGenOptions().getHeight(); - - auto zones = map.getZones(); - vstd::erase_if(zones, [](const std::pair> & pr) - { - return pr.second->getType() == ETemplateZoneType::WATER; - }); - bool underground = map.getMapGenOptions().getHasTwoLevels(); - - findPathsBetweenZones(); - placeOnGrid(rand); - - /* - Fruchterman-Reingold algorithm - - Let's assume we try to fit N circular zones with radius = size on a map - Connected zones attract, intersecting zones and map boundaries push back - */ - - TZoneVector zonesVector(zones.begin(), zones.end()); - assert (zonesVector.size()); - - RandomGeneratorUtil::randomShuffle(zonesVector, *rand); - - //0. set zone sizes and surface / underground level - prepareZones(zones, zonesVector, underground, rand); - - std::map, float3> bestSolution; - - TForceVector forces; - TForceVector totalForces; // both attraction and pushback, overcomplicated? - TDistanceVector distances; - TDistanceVector overlaps; - - auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool - { - bool improvement = false; - - float totalDistance = 0; - float totalOverlap = 0; - for (const auto& zone : distances) //find most misplaced zone - { - totalDistance += zone.second; - float overlap = overlaps[zone.first]; - totalOverlap += overlap; - } - - //check fitness function - if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1)) - { - //multiplication is better for auto-scaling, but stops working if one factor is 0 - improvement = true; - } - - //Save best solution - if (improvement) - { - bestTotalDistance = totalDistance; - bestTotalOverlap = totalOverlap; - - for (const auto& zone : zones) - bestSolution[zone.second] = zone.second->getCenter(); - } - -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); -#endif - - return improvement; - }; - - //Start with low stiffness. Bigger graphs need more time and more flexibility - for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;) - { - //1. attract connected zones - attractConnectedZones(zones, forces, distances); - for(const auto & zone : forces) - { - zone.first->setCenter (zone.first->getCenter() + zone.second); - totalForces[zone.first] = zone.second; //override - } - - //2. separate overlapping zones - separateOverlappingZones(zones, forces, overlaps); - for(const auto & zone : forces) - { - zone.first->setCenter (zone.first->getCenter() + zone.second); - totalForces[zone.first] += zone.second; //accumulate - } - - bool improved = evaluateSolution(); - - if (!improved) - { - //3. now perform drastic movement of zone that is completely not linked - //TODO: Don't do this is fitness was improved - moveOneZone(zones, totalForces, distances, overlaps); - - improved |= evaluateSolution(); - } - - if (!improved) - { - //Only cool down if we didn't see any improvement - stifness *= stiffnessIncreaseFactor; - } - - } - - logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap); - for(const auto & zone : zones) //finalize zone positions - { - zone.second->setPos (cords (bestSolution[zone.second])); -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString()); -#endif - } -} - -void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) -{ - std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map - - int zonesOnLevel[2] = { 0, 0 }; - - //even distribution for surface / underground zones. Surface zones always have priority. - - TZoneVector zonesToPlace; - std::map levels; - - //first pass - determine fixed surface for zones - for(const auto & zone : zonesVector) - { - if (!underground) //this step is ignored - zonesToPlace.push_back(zone); - else //place players depending on their factions - { - if(std::optional owner = zone.second->getOwner()) - { - auto player = PlayerColor(*owner - 1); - auto playerSettings = map.getMapGenOptions().getPlayersSettings(); - FactionID faction = FactionID::RANDOM; - if (playerSettings.size() > player) - { - faction = std::next(playerSettings.begin(), player)->second.getStartingTown(); - } - else - { - logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first); - } - - if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized - zonesToPlace.push_back(zone); - else - { - auto & tt = (*VLC->townh)[faction]->nativeTerrain; - if(tt == ETerrainId::NONE) - { - //any / random - zonesToPlace.push_back(zone); - } - else - { - const auto & terrainType = VLC->terrainTypeHandler->getById(tt); - if(terrainType->isUnderground() && !terrainType->isSurface()) - { - //underground only - zonesOnLevel[1]++; - levels[zone.first] = 1; - } - else - { - //surface - zonesOnLevel[0]++; - levels[zone.first] = 0; - } - } - } - } - else //no starting zone or no underground altogether - { - zonesToPlace.push_back(zone); - } - } - } - for(const auto & zone : zonesToPlace) - { - if (underground) //only then consider underground zones - { - int level = 0; - if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones - level = 1; - else - level = 0; - - levels[zone.first] = level; - zonesOnLevel[level]++; - } - else - levels[zone.first] = 0; - } - - for(const auto & zone : zonesVector) - { - int level = levels[zone.first]; - totalSize[level] += (zone.second->getSize() * zone.second->getSize()); - float3 center = zone.second->getCenter(); - center.z = level; - zone.second->setCenter(center); - } - - /* - prescale zones - - formula: sum((prescaler*n)^2)*pi = WH - - prescaler = sqrt((WH)/(sum(n^2)*pi)) - */ - - std::vector prescaler = { 0, 0 }; - for (int i = 0; i < 2; i++) - prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); - mapSize = static_cast(sqrt(width * height)); - for(const auto & zone : zones) - { - zone.second->setSize(static_cast(zone.second->getSize() * prescaler[zone.second->getCenter().z])); - } -} - -void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const -{ - for(const auto & zone : zones) - { - float3 forceVector(0, 0, 0); - float3 pos = zone.second->getCenter(); - float totalDistance = 0; - - for (const auto & connection : zone.second->getConnections()) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - if (connection.getZoneA() == connection.getZoneB()) - { - //Do not consider self-connections - continue; - } - - auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; - float3 otherZoneCenter = otherZone->getCenter(); - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - - forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value - - //Attract zone centers always - - float minDistance = 0; - - if (pos.z != otherZoneCenter.z) - minDistance = 0; //zones on different levels can overlap completely - else - minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates - - if (distance > minDistance) - totalDistance += (distance - minDistance); - } - distances[zone.second] = totalDistance; - forceVector.z = 0; //operator - doesn't preserve z coordinate :/ - forces[zone.second] = forceVector; - } -} - -void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps) -{ - for(const auto & zone : zones) - { - float3 forceVector(0, 0, 0); - float3 pos = zone.second->getCenter(); - - float overlap = 0; - //separate overlapping zones - for(const auto & otherZone : zones) - { - float3 otherZoneCenter = otherZone.second->getCenter(); - //zones on different levels don't push away - if (zone == otherZone || pos.z != otherZoneCenter.z) - continue; - - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; - if (distance < minDistance) - { - float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; - //negative value - localForce *= scaleForceBetweenZones(zone.second, otherZone.second); - forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); - overlap += (minDistance - distance); //overlapping of small zones hurts us more - } - } - - //move zones away from boundaries - //do not scale boundary distance - zones tend to get squashed - float size = zone.second->getSize() / mapSize; - - auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y) - { - float3 boundary = float3(x, y, pos.z); - auto distance = static_cast(pos.dist2d(boundary)); - overlap += std::max(0, distance - size); //check if we're closer to map boundary than value of zone size - forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value - }; - if (pos.x < size) - { - pushAwayFromBoundary(0, pos.y); - } - if (pos.x > 1 - size) - { - pushAwayFromBoundary(1, pos.y); - } - if (pos.y < size) - { - pushAwayFromBoundary(pos.x, 0); - } - if (pos.y > 1 - size) - { - pushAwayFromBoundary(pos.x, 1); - } - - //Always move repulsive zones away, no matter their distance - //TODO: Consider z plane? - for (auto& connection : zone.second->getConnections()) - { - if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) - { - auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; - float3 otherZoneCenter = otherZone->getCenter(); - - //TODO: Roll into lambda? - auto distance = static_cast(pos.dist2d(otherZoneCenter)); - float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; - float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; - localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]); - forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone); - } - } - - overlaps[zone.second] = overlap; - forceVector.z = 0; //operator - doesn't preserve z coordinate :/ - forces[zone.second] = forceVector; - } -} - -void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps) -{ - //The more zones, the greater total distance expected - //Also, higher stiffness make expected movement lower - const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness); - - typedef std::pair> Misplacement; - std::vector misplacedZones; - - float totalDistance = 0; - float totalOverlap = 0; - for (const auto& zone : distances) //find most misplaced zone - { - if (vstd::contains(lastSwappedZones, zone.first->getId())) - { - continue; - } - totalDistance += zone.second; - float overlap = overlaps[zone.first]; - totalOverlap += overlap; - //if distance to actual movement is long, the zone is misplaced - float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); - if (ratio > maxDistanceMovementRatio) - { - misplacedZones.emplace_back(std::make_pair(ratio, zone.first)); - } - } - - if (misplacedZones.empty()) - return; - - boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs) - { - return lhs.first > rhs.first; //Largest displacement first - }); - -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); -#endif - - if (misplacedZones.size() >= 2) - { - //Swap 2 misplaced zones - - auto firstZone = misplacedZones.front().second; - std::shared_ptr secondZone; - std::set connectedZones; - for (const auto& connection : firstZone->getConnections()) - { - switch (connection.getConnectionType()) - { - //Do not consider virtual connections for graph distance - case rmg::EConnectionType::REPULSIVE: - case rmg::EConnectionType::FORCE_PORTAL: - continue; - } - if (connection.getZoneA() == connection.getZoneB()) - { - //Do not consider self-connections - continue; - } - connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); - } - - auto level = firstZone->getCenter().z; - for (size_t i = 1; i < misplacedZones.size(); i++) - { - //Only swap zones on the same level - //Don't swap zones that should be connected (Jebus) - - if (misplacedZones[i].second->getCenter().z == level && - !vstd::contains(connectedZones, misplacedZones[i].second->getId())) - { - secondZone = misplacedZones[i].second; - break; - } - } - if (secondZone) - { -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); -#endif - - auto firstCenter = firstZone->getCenter(); - auto secondCenter = secondZone->getCenter(); - firstZone->setCenter(secondCenter); - secondZone->setCenter(firstCenter); - - lastSwappedZones.insert(firstZone->getId()); - lastSwappedZones.insert(secondZone->getId()); - return; - } - } - lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next - - //find most distant zone that should be attracted and move inside it - std::shared_ptr targetZone; - auto misplacedZone = misplacedZones.front().second; - float3 ourCenter = misplacedZone->getCenter(); - - if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1))) - { - //Move one zone towards most distant zone to reduce distance - - float maxDistance = 0; - for (auto con : misplacedZone->getConnections()) - { - if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE) - { - continue; - } - - auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())]; - float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); - if (distance > maxDistance) - { - maxDistance = distance; - targetZone = otherZone; - } - } - if (targetZone) - { - float3 vec = targetZone->getCenter() - ourCenter; - float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); -#endif - - misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size - } - } - else - { - //Move misplaced zone away from overlapping zone - - float maxOverlap = 0; - for(const auto & otherZone : zones) - { - float3 otherZoneCenter = otherZone.second->getCenter(); - - if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) - continue; - - auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); - if (distance > maxOverlap) - { - maxOverlap = distance; - targetZone = otherZone.second; - } - } - if (targetZone) - { - float3 vec = ourCenter - targetZone->getCenter(); - float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; -#ifdef ZONE_PLACEMENT_LOG - logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); -#endif - - misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated - } - } - //Don't swap that zone in next iteration - lastSwappedZones.insert(misplacedZone->getId()); -} - -float CZonePlacer::metric (const int3 &A, const int3 &B) const -{ - return A.dist2dSQ(B); - -} - -void CZonePlacer::assignZones(vstd::RNG * rand) -{ - logGlobal->info("Starting zone colouring"); - - auto width = map.getMapGenOptions().getWidth(); - auto height = map.getMapGenOptions().getHeight(); - - - auto zones = map.getZones(); - vstd::erase_if(zones, [](const std::pair> & pr) - { - return pr.second->getType() == ETemplateZoneType::WATER; - }); - - using Dpair = std::pair, float>; - std::vector distances; - distances.reserve(zones.size()); - - //now place zones correctly and assign tiles to each zone - - auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool - { - //bigger zones have smaller distance - return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); - }; - - auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool - { - //bigger zones have smaller distance - return lhs.second < rhs.second; - }; - - auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr & zone) -> void - { - int3 total(0, 0, 0); - auto tiles = zone->area()->getTiles(); - for(const auto & tile : tiles) - { - total += tile; - } - int size = static_cast(tiles.size()); - assert(size); - auto newPos = int3(total.x / size, total.y / size, total.z / size); - zone->setPos(newPos); - zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z)); - }; - - int levels = map.levels(); - - // Find current center of mass for each zone. Move zone to that center to balance zones sizes - std::vector zonesOnLevel; - for(int level = 0; level < levels; level++) - { - zonesOnLevel.push_back(map.getZonesOnLevel(level)); - } - - int3 pos; - - for(pos.z = 0; pos.z < levels; pos.z++) - { - for(pos.x = 0; pos.x < width; pos.x++) - { - for(pos.y = 0; pos.y < height; pos.y++) - { - distances.clear(); - for(const auto & zone : zonesOnLevel[pos.z]) - { - distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); - } - boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone - } - } - } - - for(const auto & zone : zones) - { - if(zone.second->area()->empty()) - throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); - - moveZoneToCenterOfMass(zone.second); - } - - for(const auto & zone : zones) - zone.second->clearTiles(); //now populate them again - - PenroseTiling penrose; - for (int level = 0; level < levels; level++) - { - //Create different tiling for each level - - auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand); - - // Assign zones to closest Penrose vertex - std::map, std::set> vertexMapping; - - for (const auto & vertex : vertices) - { - distances.clear(); - for(const auto & zone : zonesOnLevel[level]) - { - distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); - } - auto closestZone = boost::min_element(distances, compareByDistance)->first; - - vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone - } - - //Assign actual tiles to each zone - pos.z = level; - for (pos.x = 0; pos.x < width; pos.x++) - { - for (pos.y = 0; pos.y < height; pos.y++) - { - distances.clear(); - for(const auto & zoneVertex : vertexMapping) - { - auto zone = zoneVertex.first; - for (const auto & vertex : zoneVertex.second) - { - distances.emplace_back(zone, metric(pos, vertex)); - } - } - - //Tile closest to vertex belongs to zone - auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; - closestZone->area()->add(pos); - map.setZoneID(pos, closestZone->getId()); - } - } - - for(const auto & zone : zonesOnLevel[level]) - { - if(zone.second->area()->empty()) - { - // FIXME: Some vertices are duplicated, but it's not a source of problem - logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); - for (const auto & vertex : vertices) - { - logGlobal->warn("Penrose Vertex: %s", vertex.toString()); - } - throw rmgException("Empty zone after Penrose tiling"); - } - } - } - - //set position (town position) to center of mass of irregular zone - for(const auto & zone : zones) - { - moveZoneToCenterOfMass(zone.second); - - //TODO: similar for islands - #define CREATE_FULL_UNDERGROUND true //consider linking this with water amount - if (zone.second->isUnderground()) - { - if (!CREATE_FULL_UNDERGROUND) - { - auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f); - for(const auto & t : discardTiles) - zone.second->area()->erase(t); - } - - //make sure that terrain inside zone is not a rock - - auto v = zone.second->area()->getTilesVector(); - map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN); - } - } - logGlobal->info("Finished zone colouring"); -} - -const TDistanceMap& CZonePlacer::getDistanceMap() -{ - return distancesBetweenZones; -} - -VCMI_LIB_NAMESPACE_END +/* + * CZonePlacer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CZonePlacer.h" + +#include "../TerrainHandler.h" +#include "../entities/faction/CFaction.h" +#include "../entities/faction/CTownHandler.h" +#include "../mapping/CMap.h" +#include "../mapping/CMapEditManager.h" +#include "../VCMI_Lib.h" +#include "CMapGenOptions.h" +#include "RmgMap.h" +#include "Zone.h" +#include "Functions.h" +#include "PenroseTiling.h" + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +//#define ZONE_PLACEMENT_LOG true + +CZonePlacer::CZonePlacer(RmgMap & map) + : width(0), height(0), mapSize(0), + gravityConstant(1e-3f), + stiffnessConstant(3e-3f), + stifness(0), + stiffnessIncreaseFactor(1.03f), + bestTotalDistance(1e10), + bestTotalOverlap(1e10), + map(map) +{ +} + +int3 CZonePlacer::cords(const float3 & f) const +{ + return int3(static_cast(std::max(0.f, (f.x * map.width()) - 1)), static_cast(std::max(0.f, (f.y * map.height() - 1))), f.z); +} + +float CZonePlacer::getDistance (float distance) const +{ + return (distance ? distance * distance : 1e-6f); +} + +void CZonePlacer::findPathsBetweenZones() +{ + auto zones = map.getZones(); + + std::set> zonesToCheck; + + // Iterate through each pair of nodes in the graph + + for (const auto& zone : zones) + { + int start = zone.first; + distancesBetweenZones[start][start] = 0; // Distance from a node to itself is 0 + + std::queue q; + std::map visited; + visited[start] = true; + q.push(start); + + // Perform Breadth-First Search from the starting node + while (!q.empty()) + { + int current = q.front(); + q.pop(); + + const auto& currentZone = zones.at(current); + const auto& connectedZoneIds = currentZone->getConnections(); + + for (auto & connection : connectedZoneIds) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + auto neighbor = connection.getOtherZoneId(current); + + if (current == neighbor) + { + //Do not consider self-connections + continue; + } + + if (!visited[neighbor]) + { + visited[neighbor] = true; + q.push(neighbor); + distancesBetweenZones[start][neighbor] = distancesBetweenZones[start][current] + 1; + } + } + } + } +} + +void CZonePlacer::placeOnGrid(vstd::RNG* rand) +{ + auto zones = map.getZones(); + assert(zones.size()); + + //Make sure there are at least as many grid fields as the number of zones + size_t gridSize = std::ceil(std::sqrt(zones.size())); + + typedef boost::multi_array, 2> GridType; + GridType grid(boost::extents[gridSize][gridSize]); + + TZoneVector zonesVector(zones.begin(), zones.end()); + + //Place first zone + + auto firstZone = zonesVector[0].second; + size_t x = 0; + size_t y = 0; + + auto getRandomEdge = [rand, gridSize](size_t& x, size_t& y) + { + switch (rand->nextInt(0, 3) % 4) + { + case 0: + x = 0; + y = gridSize / 2; + break; + case 1: + x = gridSize - 1; + y = gridSize / 2; + break; + case 2: + x = gridSize / 2; + y = 0; + break; + case 3: + x = gridSize / 2; + y = gridSize - 1; + break; + } + }; + + switch (firstZone->getType()) + { + case ETemplateZoneType::PLAYER_START: + case ETemplateZoneType::CPU_START: + if (firstZone->getConnectedZoneIds().size() > 2) + { + getRandomEdge(x, y); + } + else + { + //Random corner + if (rand->nextInt(0, 1) == 1) + { + x = 0; + } + else + { + x = gridSize - 1; + } + if (rand->nextInt(0, 1) == 1) + { + y = 0; + } + else + { + y = gridSize - 1; + } + } + break; + case ETemplateZoneType::TREASURE: + if (gridSize & 1) //odd + { + x = y = (gridSize / 2); + } + else + { + //One of 4 squares in the middle + x = (gridSize / 2) - 1 + rand->nextInt(0, 1); + y = (gridSize / 2) - 1 + rand->nextInt(0, 1); + } + break; + case ETemplateZoneType::JUNCTION: + getRandomEdge(x, y); + break; + } + grid[x][y] = firstZone; + + //Ignore z placement for simplicity + + for (size_t i = 1; i < zones.size(); i++) + { + auto zone = zonesVector[i].second; + auto connectedZoneIds = zone->getConnectedZoneIds(); + + float maxDistance = -1000.0; + int3 mostDistantPlace; + + //Iterate over free positions + for (size_t freeX = 0; freeX < gridSize; ++freeX) + { + for (size_t freeY = 0; freeY < gridSize; ++freeY) + { + if (!grid[freeX][freeY]) + { + //There is free space left here + int3 potentialPos(freeX, freeY, 0); + + //Compute distance to every existing zone + + float distance = 0; + for (size_t existingX = 0; existingX < gridSize; ++existingX) + { + for (size_t existingY = 0; existingY < gridSize; ++existingY) + { + auto existingZone = grid[existingX][existingY]; + if (existingZone) + { + //There is already zone here + float localDistance = 0.0f; + + auto graphDistance = distancesBetweenZones[zone->getId()][existingZone->getId()]; + if (graphDistance > 1) + { + //No direct connection + localDistance = potentialPos.dist2d(int3(existingX, existingY, 0)) * graphDistance; + } + else + { + //Has direct connection - place as close as possible + localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0)); + } + + localDistance *= scaleForceBetweenZones(zone, existingZone); + + distance += localDistance; + } + } + } + if (distance > maxDistance) + { + maxDistance = distance; + mostDistantPlace = potentialPos; + } + } + } + } + + //Place in a free slot + grid[mostDistantPlace.x][mostDistantPlace.y] = zone; + } + + //TODO: toggle with a flag +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Initial zone grid:"); + for (size_t x = 0; x < gridSize; ++x) + { + std::string s; + for (size_t y = 0; y < gridSize; ++y) + { + if (grid[x][y]) + { + s += (boost::format("%3d ") % grid[x][y]->getId()).str(); + } + else + { + s += " -- "; + } + } + logGlobal->trace(s); + } +#endif + + //Set initial position for zones - random position in square centered around (x, y) + for (size_t x = 0; x < gridSize; ++x) + { + for (size_t y = 0; y < gridSize; ++y) + { + auto zone = grid[x][y]; + if (zone) + { + //i.e. for grid size 5 we get range (0.25 - 4.75) + auto targetX = rand->nextDouble(x + 0.25f, x + 0.75f); + vstd::abetween(targetX, 0.5, gridSize - 0.5); + auto targetY = rand->nextDouble(y + 0.25f, y + 0.75f); + vstd::abetween(targetY, 0.5, gridSize - 0.5); + + zone->setCenter(float3(targetX / gridSize, targetY / gridSize, zone->getPos().z)); + } + } + } +} + +float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr zoneA, const std::shared_ptr zoneB) const +{ + if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game + { + int firstPlayer = zoneA->getOwner().value(); + int secondPlayer = zoneB->getOwner().value(); + + //Players with lower indexes (especially 1 and 2) will be placed further apart + + return (1.0f + (2.0f / (firstPlayer * secondPlayer))); + } + else + { + return 1; + } +} + +void CZonePlacer::placeZones(vstd::RNG * rand) +{ + logGlobal->info("Starting zone placement"); + + width = map.getMapGenOptions().getWidth(); + height = map.getMapGenOptions().getHeight(); + + auto zones = map.getZones(); + vstd::erase_if(zones, [](const std::pair> & pr) + { + return pr.second->getType() == ETemplateZoneType::WATER; + }); + bool underground = map.getMapGenOptions().getHasTwoLevels(); + + findPathsBetweenZones(); + placeOnGrid(rand); + + /* + Fruchterman-Reingold algorithm + + Let's assume we try to fit N circular zones with radius = size on a map + Connected zones attract, intersecting zones and map boundaries push back + */ + + TZoneVector zonesVector(zones.begin(), zones.end()); + assert (zonesVector.size()); + + RandomGeneratorUtil::randomShuffle(zonesVector, *rand); + + //0. set zone sizes and surface / underground level + prepareZones(zones, zonesVector, underground, rand); + + std::map, float3> bestSolution; + + TForceVector forces; + TForceVector totalForces; // both attraction and pushback, overcomplicated? + TDistanceVector distances; + TDistanceVector overlaps; + + auto evaluateSolution = [this, zones, &distances, &overlaps, &bestSolution]() -> bool + { + bool improvement = false; + + float totalDistance = 0; + float totalOverlap = 0; + for (const auto& zone : distances) //find most misplaced zone + { + totalDistance += zone.second; + float overlap = overlaps[zone.first]; + totalOverlap += overlap; + } + + //check fitness function + if ((totalDistance + 1) * (totalOverlap + 1) < (bestTotalDistance + 1) * (bestTotalOverlap + 1)) + { + //multiplication is better for auto-scaling, but stops working if one factor is 0 + improvement = true; + } + + //Save best solution + if (improvement) + { + bestTotalDistance = totalDistance; + bestTotalOverlap = totalOverlap; + + for (const auto& zone : zones) + bestSolution[zone.second] = zone.second->getCenter(); + } + +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Total distance between zones after this iteration: %2.4f, Total overlap: %2.4f, Improved: %s", totalDistance, totalOverlap , improvement); +#endif + + return improvement; + }; + + //Start with low stiffness. Bigger graphs need more time and more flexibility + for (stifness = stiffnessConstant / zones.size(); stifness <= stiffnessConstant;) + { + //1. attract connected zones + attractConnectedZones(zones, forces, distances); + for(const auto & zone : forces) + { + zone.first->setCenter (zone.first->getCenter() + zone.second); + totalForces[zone.first] = zone.second; //override + } + + //2. separate overlapping zones + separateOverlappingZones(zones, forces, overlaps); + for(const auto & zone : forces) + { + zone.first->setCenter (zone.first->getCenter() + zone.second); + totalForces[zone.first] += zone.second; //accumulate + } + + bool improved = evaluateSolution(); + + if (!improved) + { + //3. now perform drastic movement of zone that is completely not linked + //TODO: Don't do this is fitness was improved + moveOneZone(zones, totalForces, distances, overlaps); + + improved |= evaluateSolution(); + } + + if (!improved) + { + //Only cool down if we didn't see any improvement + stifness *= stiffnessIncreaseFactor; + } + + } + + logGlobal->trace("Best fitness reached: total distance %2.4f, total overlap %2.4f", bestTotalDistance, bestTotalOverlap); + for(const auto & zone : zones) //finalize zone positions + { + zone.second->setPos (cords (bestSolution[zone.second])); +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Placed zone %d at relative position %s and coordinates %s", zone.first, zone.second->getCenter().toString(), zone.second->getPos().toString()); +#endif + } +} + +void CZonePlacer::prepareZones(TZoneMap &zones, TZoneVector &zonesVector, const bool underground, vstd::RNG * rand) +{ + std::vector totalSize = { 0, 0 }; //make sure that sum of zone sizes on surface and uderground match size of the map + + int zonesOnLevel[2] = { 0, 0 }; + + //even distribution for surface / underground zones. Surface zones always have priority. + + TZoneVector zonesToPlace; + std::map levels; + + //first pass - determine fixed surface for zones + for(const auto & zone : zonesVector) + { + if (!underground) //this step is ignored + zonesToPlace.push_back(zone); + else //place players depending on their factions + { + if(std::optional owner = zone.second->getOwner()) + { + auto player = PlayerColor(*owner - 1); + auto playerSettings = map.getMapGenOptions().getPlayersSettings(); + FactionID faction = FactionID::RANDOM; + if (playerSettings.size() > player) + { + faction = std::next(playerSettings.begin(), player)->second.getStartingTown(); + } + else + { + logGlobal->trace("Player %d (starting zone %d) does not participate in game", player.getNum(), zone.first); + } + + if (faction == FactionID::RANDOM) //TODO: check this after a town has already been randomized + zonesToPlace.push_back(zone); + else + { + auto & tt = (*VLC->townh)[faction]->nativeTerrain; + if(tt == ETerrainId::NONE) + { + //any / random + zonesToPlace.push_back(zone); + } + else + { + const auto & terrainType = VLC->terrainTypeHandler->getById(tt); + if(terrainType->isUnderground() && !terrainType->isSurface()) + { + //underground only + zonesOnLevel[1]++; + levels[zone.first] = 1; + } + else + { + //surface + zonesOnLevel[0]++; + levels[zone.first] = 0; + } + } + } + } + else //no starting zone or no underground altogether + { + zonesToPlace.push_back(zone); + } + } + } + for(const auto & zone : zonesToPlace) + { + if (underground) //only then consider underground zones + { + int level = 0; + if (zonesOnLevel[1] < zonesOnLevel[0]) //only if there are less underground zones + level = 1; + else + level = 0; + + levels[zone.first] = level; + zonesOnLevel[level]++; + } + else + levels[zone.first] = 0; + } + + for(const auto & zone : zonesVector) + { + int level = levels[zone.first]; + totalSize[level] += (zone.second->getSize() * zone.second->getSize()); + float3 center = zone.second->getCenter(); + center.z = level; + zone.second->setCenter(center); + } + + /* + prescale zones + + formula: sum((prescaler*n)^2)*pi = WH + + prescaler = sqrt((WH)/(sum(n^2)*pi)) + */ + + std::vector prescaler = { 0, 0 }; + for (int i = 0; i < 2; i++) + prescaler[i] = std::sqrt((width * height) / (totalSize[i] * PI_CONSTANT)); + mapSize = static_cast(sqrt(width * height)); + for(const auto & zone : zones) + { + zone.second->setSize(static_cast(zone.second->getSize() * prescaler[zone.second->getCenter().z])); + } +} + +void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces, TDistanceVector & distances) const +{ + for(const auto & zone : zones) + { + float3 forceVector(0, 0, 0); + float3 pos = zone.second->getCenter(); + float totalDistance = 0; + + for (const auto & connection : zone.second->getConnections()) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections + continue; + } + + auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; + float3 otherZoneCenter = otherZone->getCenter(); + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + + forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value + + //Attract zone centers always + + float minDistance = 0; + + if (pos.z != otherZoneCenter.z) + minDistance = 0; //zones on different levels can overlap completely + else + minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; //scale down to (0,1) coordinates + + if (distance > minDistance) + totalDistance += (distance - minDistance); + } + distances[zone.second] = totalDistance; + forceVector.z = 0; //operator - doesn't preserve z coordinate :/ + forces[zone.second] = forceVector; + } +} + +void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces, TDistanceVector &overlaps) +{ + for(const auto & zone : zones) + { + float3 forceVector(0, 0, 0); + float3 pos = zone.second->getCenter(); + + float overlap = 0; + //separate overlapping zones + for(const auto & otherZone : zones) + { + float3 otherZoneCenter = otherZone.second->getCenter(); + //zones on different levels don't push away + if (zone == otherZone || pos.z != otherZoneCenter.z) + continue; + + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + float minDistance = (zone.second->getSize() + otherZone.second->getSize()) / mapSize; + if (distance < minDistance) + { + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; + //negative value + localForce *= scaleForceBetweenZones(zone.second, otherZone.second); + forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f); + overlap += (minDistance - distance); //overlapping of small zones hurts us more + } + } + + //move zones away from boundaries + //do not scale boundary distance - zones tend to get squashed + float size = zone.second->getSize() / mapSize; + + auto pushAwayFromBoundary = [&forceVector, pos, size, &overlap, this](float x, float y) + { + float3 boundary = float3(x, y, pos.z); + auto distance = static_cast(pos.dist2d(boundary)); + overlap += std::max(0, distance - size); //check if we're closer to map boundary than value of zone size + forceVector -= (boundary - pos) * (size - distance) / this->getDistance(distance) * this->stifness; //negative value + }; + if (pos.x < size) + { + pushAwayFromBoundary(0, pos.y); + } + if (pos.x > 1 - size) + { + pushAwayFromBoundary(1, pos.y); + } + if (pos.y < size) + { + pushAwayFromBoundary(pos.x, 0); + } + if (pos.y > 1 - size) + { + pushAwayFromBoundary(pos.x, 1); + } + + //Always move repulsive zones away, no matter their distance + //TODO: Consider z plane? + for (auto& connection : zone.second->getConnections()) + { + if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE) + { + auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())]; + float3 otherZoneCenter = otherZone->getCenter(); + + //TODO: Roll into lambda? + auto distance = static_cast(pos.dist2d(otherZoneCenter)); + float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize; + float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness; + localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]); + forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone); + } + } + + overlaps[zone.second] = overlap; + forceVector.z = 0; //operator - doesn't preserve z coordinate :/ + forces[zone.second] = forceVector; + } +} + +void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps) +{ + //The more zones, the greater total distance expected + //Also, higher stiffness make expected movement lower + const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness); + + typedef std::pair> Misplacement; + std::vector misplacedZones; + + float totalDistance = 0; + float totalOverlap = 0; + for (const auto& zone : distances) //find most misplaced zone + { + if (vstd::contains(lastSwappedZones, zone.first->getId())) + { + continue; + } + totalDistance += zone.second; + float overlap = overlaps[zone.first]; + totalOverlap += overlap; + //if distance to actual movement is long, the zone is misplaced + float ratio = (zone.second + overlap) / static_cast(totalForces[zone.first].mag()); + if (ratio > maxDistanceMovementRatio) + { + misplacedZones.emplace_back(std::make_pair(ratio, zone.first)); + } + } + + if (misplacedZones.empty()) + return; + + boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs) + { + return lhs.first > rhs.first; //Largest displacement first + }); + +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first); +#endif + + if (misplacedZones.size() >= 2) + { + //Swap 2 misplaced zones + + auto firstZone = misplacedZones.front().second; + std::shared_ptr secondZone; + std::set connectedZones; + for (const auto& connection : firstZone->getConnections()) + { + switch (connection.getConnectionType()) + { + //Do not consider virtual connections for graph distance + case rmg::EConnectionType::REPULSIVE: + case rmg::EConnectionType::FORCE_PORTAL: + continue; + } + if (connection.getZoneA() == connection.getZoneB()) + { + //Do not consider self-connections + continue; + } + connectedZones.insert(connection.getOtherZoneId(firstZone->getId())); + } + + auto level = firstZone->getCenter().z; + for (size_t i = 1; i < misplacedZones.size(); i++) + { + //Only swap zones on the same level + //Don't swap zones that should be connected (Jebus) + + if (misplacedZones[i].second->getCenter().z == level && + !vstd::contains(connectedZones, misplacedZones[i].second->getId())) + { + secondZone = misplacedZones[i].second; + break; + } + } + if (secondZone) + { +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Swapping two misplaced zones %d and %d", firstZone->getId(), secondZone->getId()); +#endif + + auto firstCenter = firstZone->getCenter(); + auto secondCenter = secondZone->getCenter(); + firstZone->setCenter(secondCenter); + secondZone->setCenter(firstCenter); + + lastSwappedZones.insert(firstZone->getId()); + lastSwappedZones.insert(secondZone->getId()); + return; + } + } + lastSwappedZones.clear(); //If we didn't swap zones in this iteration, we can do it in the next + + //find most distant zone that should be attracted and move inside it + std::shared_ptr targetZone; + auto misplacedZone = misplacedZones.front().second; + float3 ourCenter = misplacedZone->getCenter(); + + if ((totalDistance / (bestTotalDistance + 1)) > (totalOverlap / (bestTotalOverlap + 1))) + { + //Move one zone towards most distant zone to reduce distance + + float maxDistance = 0; + for (auto con : misplacedZone->getConnections()) + { + if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE) + { + continue; + } + + auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())]; + float distance = static_cast(otherZone->getCenter().dist2dSQ(ourCenter)); + if (distance > maxDistance) + { + maxDistance = distance; + targetZone = otherZone; + } + } + if (targetZone) + { + float3 vec = targetZone->getCenter() - ourCenter; + float newDistanceBetweenZones = (std::max(misplacedZone->getSize(), targetZone->getSize())) / mapSize; +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Trying to move zone %d %s towards %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif + + misplacedZone->setCenter(targetZone->getCenter() - vec.unitVector() * newDistanceBetweenZones); //zones should now overlap by half size + } + } + else + { + //Move misplaced zone away from overlapping zone + + float maxOverlap = 0; + for(const auto & otherZone : zones) + { + float3 otherZoneCenter = otherZone.second->getCenter(); + + if (otherZone.second == misplacedZone || otherZoneCenter.z != ourCenter.z) + continue; + + auto distance = static_cast(otherZoneCenter.dist2dSQ(ourCenter)); + if (distance > maxOverlap) + { + maxOverlap = distance; + targetZone = otherZone.second; + } + } + if (targetZone) + { + float3 vec = ourCenter - targetZone->getCenter(); + float newDistanceBetweenZones = (misplacedZone->getSize() + targetZone->getSize()) / mapSize; +#ifdef ZONE_PLACEMENT_LOG + logGlobal->trace("Trying to move zone %d %s away from %d %s. Direction is %s", misplacedZone->getId(), ourCenter.toString(), targetZone->getId(), targetZone->getCenter().toString(), vec.toString()); +#endif + + misplacedZone->setCenter(targetZone->getCenter() + vec.unitVector() * newDistanceBetweenZones); //zones should now be just separated + } + } + //Don't swap that zone in next iteration + lastSwappedZones.insert(misplacedZone->getId()); +} + +float CZonePlacer::metric (const int3 &A, const int3 &B) const +{ + return A.dist2dSQ(B); + +} + +void CZonePlacer::assignZones(vstd::RNG * rand) +{ + logGlobal->info("Starting zone colouring"); + + auto width = map.getMapGenOptions().getWidth(); + auto height = map.getMapGenOptions().getHeight(); + + + auto zones = map.getZones(); + vstd::erase_if(zones, [](const std::pair> & pr) + { + return pr.second->getType() == ETemplateZoneType::WATER; + }); + + using Dpair = std::pair, float>; + std::vector distances; + distances.reserve(zones.size()); + + //now place zones correctly and assign tiles to each zone + + auto compareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool + { + //bigger zones have smaller distance + return lhs.second / lhs.first->getSize() < rhs.second / rhs.first->getSize(); + }; + + auto simpleCompareByDistance = [](const Dpair & lhs, const Dpair & rhs) -> bool + { + //bigger zones have smaller distance + return lhs.second < rhs.second; + }; + + auto moveZoneToCenterOfMass = [width, height](const std::shared_ptr & zone) -> void + { + int3 total(0, 0, 0); + auto tiles = zone->area()->getTiles(); + for(const auto & tile : tiles) + { + total += tile; + } + int size = static_cast(tiles.size()); + assert(size); + auto newPos = int3(total.x / size, total.y / size, total.z / size); + zone->setPos(newPos); + zone->setCenter(float3(float(newPos.x) / width, float(newPos.y) / height, newPos.z)); + }; + + int levels = map.levels(); + + // Find current center of mass for each zone. Move zone to that center to balance zones sizes + std::vector zonesOnLevel; + for(int level = 0; level < levels; level++) + { + zonesOnLevel.push_back(map.getZonesOnLevel(level)); + } + + int3 pos; + + for(pos.z = 0; pos.z < levels; pos.z++) + { + for(pos.x = 0; pos.x < width; pos.x++) + { + for(pos.y = 0; pos.y < height; pos.y++) + { + distances.clear(); + for(const auto & zone : zonesOnLevel[pos.z]) + { + distances.emplace_back(zone.second, static_cast(pos.dist2dSQ(zone.second->getPos()))); + } + boost::min_element(distances, compareByDistance)->first->area()->add(pos); //closest tile belongs to zone + } + } + } + + for(const auto & zone : zones) + { + if(zone.second->area()->empty()) + throw rmgException("Empty zone is generated, probably RMG template is inappropriate for map size"); + + moveZoneToCenterOfMass(zone.second); + } + + for(const auto & zone : zones) + zone.second->clearTiles(); //now populate them again + + PenroseTiling penrose; + for (int level = 0; level < levels; level++) + { + //Create different tiling for each level + + auto vertices = penrose.generatePenroseTiling(zonesOnLevel[level].size(), rand); + + // Assign zones to closest Penrose vertex + std::map, std::set> vertexMapping; + + for (const auto & vertex : vertices) + { + distances.clear(); + for(const auto & zone : zonesOnLevel[level]) + { + distances.emplace_back(zone.second, zone.second->getCenter().dist2dSQ(float3(vertex.x(), vertex.y(), level))); + } + auto closestZone = boost::min_element(distances, compareByDistance)->first; + + vertexMapping[closestZone].insert(int3(vertex.x() * width, vertex.y() * height, level)); //Closest vertex belongs to zone + } + + //Assign actual tiles to each zone + pos.z = level; + for (pos.x = 0; pos.x < width; pos.x++) + { + for (pos.y = 0; pos.y < height; pos.y++) + { + distances.clear(); + for(const auto & zoneVertex : vertexMapping) + { + auto zone = zoneVertex.first; + for (const auto & vertex : zoneVertex.second) + { + distances.emplace_back(zone, metric(pos, vertex)); + } + } + + //Tile closest to vertex belongs to zone + auto closestZone = boost::min_element(distances, simpleCompareByDistance)->first; + closestZone->area()->add(pos); + map.setZoneID(pos, closestZone->getId()); + } + } + + for(const auto & zone : zonesOnLevel[level]) + { + if(zone.second->area()->empty()) + { + // FIXME: Some vertices are duplicated, but it's not a source of problem + logGlobal->error("Zone %d at %s is empty, dumping Penrose tiling", zone.second->getId(), zone.second->getCenter().toString()); + for (const auto & vertex : vertices) + { + logGlobal->warn("Penrose Vertex: %s", vertex.toString()); + } + throw rmgException("Empty zone after Penrose tiling"); + } + } + } + + //set position (town position) to center of mass of irregular zone + for(const auto & zone : zones) + { + moveZoneToCenterOfMass(zone.second); + + //TODO: similar for islands + #define CREATE_FULL_UNDERGROUND true //consider linking this with water amount + if (zone.second->isUnderground()) + { + if (!CREATE_FULL_UNDERGROUND) + { + auto discardTiles = collectDistantTiles(*zone.second, zone.second->getSize() + 1.f); + for(const auto & t : discardTiles) + zone.second->area()->erase(t); + } + + //make sure that terrain inside zone is not a rock + + auto v = zone.second->area()->getTilesVector(); + map.getMapProxy()->drawTerrain(*rand, v, ETerrainId::SUBTERRANEAN); + } + } + logGlobal->info("Finished zone colouring"); +} + +const TDistanceMap& CZonePlacer::getDistanceMap() +{ + return distancesBetweenZones; +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/rmg/RmgPath.cpp b/lib/rmg/RmgPath.cpp index b36628f98..e9ffdcc48 100644 --- a/lib/rmg/RmgPath.cpp +++ b/lib/rmg/RmgPath.cpp @@ -117,6 +117,7 @@ Path Path::search(const Tileset & dst, bool straight, std::function::max(); auto it = distances.find(pos); diff --git a/lib/rmg/modificators/ObstaclePlacer.cpp b/lib/rmg/modificators/ObstaclePlacer.cpp index 22c308daa..5447a256e 100644 --- a/lib/rmg/modificators/ObstaclePlacer.cpp +++ b/lib/rmg/modificators/ObstaclePlacer.cpp @@ -172,4 +172,4 @@ bool ObstaclePlacer::isProhibited(const rmg::Area & objArea) const return false; } -VCMI_LIB_NAMESPACE_END \ No newline at end of file +VCMI_LIB_NAMESPACE_END diff --git a/lib/serializer/BinaryDeserializer.h b/lib/serializer/BinaryDeserializer.h index 323bbf5ab..25c232ca5 100644 --- a/lib/serializer/BinaryDeserializer.h +++ b/lib/serializer/BinaryDeserializer.h @@ -13,7 +13,6 @@ #include "SerializerReflection.h" #include "ESerializationVersion.h" #include "../mapObjects/CGHeroInstance.h" -#include "../battle/BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN @@ -443,19 +442,6 @@ public: load(data[key]); } } - - //void load(BattleHexArray & data) - //{ - // uint32_t length = readAndCheckLength(); - // data.clear(); - // BattleHex hex; - // for(uint32_t i = 0; i < length; i++) - // { - // load(hex); - // data.insert(hex); - // } - //} - void load(std::string &data) { if (hasFeature(Version::COMPACT_STRING_SERIALIZATION)) diff --git a/lib/serializer/BinarySerializer.h b/lib/serializer/BinarySerializer.h index 6f2936239..738cf17c1 100644 --- a/lib/serializer/BinarySerializer.h +++ b/lib/serializer/BinarySerializer.h @@ -15,7 +15,6 @@ #include "ESerializationVersion.h" #include "Serializeable.h" #include "../mapObjects/CArmedInstance.h" -#include "../battle/BattleHexArray.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/BattleSpellMechanics.h b/lib/spells/BattleSpellMechanics.h index 2b4655cd3..860a1487c 100644 --- a/lib/spells/BattleSpellMechanics.h +++ b/lib/spells/BattleSpellMechanics.h @@ -1,86 +1,86 @@ -/* - * BattleSpellMechanics.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "ISpellMechanics.h" - -#include "effects/Effects.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleSpellCast; - -namespace spells -{ - -class BattleSpellMechanics : public BaseMechanics -{ -public: - BattleSpellMechanics(const IBattleCast * event, std::shared_ptr effects_, std::shared_ptr targetCondition_); - virtual ~BattleSpellMechanics(); - - // TODO: ??? (what's the difference compared to cast?) - void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override; - - /// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield - bool canBeCast(Problem & problem) const override; - - /// Returns false if spell can not be cast at specified target - bool canBeCastAt(const Target & target, Problem & problem) const override; - - // TODO: ??? (what's the difference compared to applyEffects?) - void cast(ServerCallback * server, const Target & target) override final; - // TODO: ??? (what's the difference compared to cast?) - void castEval(ServerCallback * server, const Target & target) override final; - - /// Returns list of affected stack using currently configured target - std::vector getAffectedStacks(const Target & target) const override final; - - /// Returns list of target types that can be targeted by spell - std::vector getTargetTypes() const override final; - - /// Returns vector of all possible destinations for specified aim type - /// index - ??? - /// current - ??? - std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final; - - /// Returns true if spell can be cast on unit - bool isReceptive(const battle::Unit * target) const override; - - /// Returns list of hexes that are affected by spell assuming cast at centralHex - BattleHexArray rangeInHexes(BattleHex centralHex) const override; - - const Spell * getSpell() const override; - - bool counteringSelector(const Bonus * bonus) const; - -private: - std::shared_ptr effects; - std::shared_ptr targetCondition; - - std::vector affectedUnits; - effects::Effects::EffectsToApply effectsToApply; - - void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); - - std::set collectTargets() const; - - void doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector); - - BattleHexArray spellRangeInHexes(BattleHex centralHex) const; - - Target transformSpellTarget(const Target & aimPoint) const; -}; - -} - - -VCMI_LIB_NAMESPACE_END +/* + * BattleSpellMechanics.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "ISpellMechanics.h" + +#include "effects/Effects.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleSpellCast; + +namespace spells +{ + +class BattleSpellMechanics : public BaseMechanics +{ +public: + BattleSpellMechanics(const IBattleCast * event, std::shared_ptr effects_, std::shared_ptr targetCondition_); + virtual ~BattleSpellMechanics(); + + // TODO: ??? (what's the difference compared to cast?) + void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const override; + + /// Returns false if spell can not be cast at all, e.g. due to not having any possible target on battlefield + bool canBeCast(Problem & problem) const override; + + /// Returns false if spell can not be cast at specified target + bool canBeCastAt(const Target & target, Problem & problem) const override; + + // TODO: ??? (what's the difference compared to applyEffects?) + void cast(ServerCallback * server, const Target & target) override final; + // TODO: ??? (what's the difference compared to cast?) + void castEval(ServerCallback * server, const Target & target) override final; + + /// Returns list of affected stack using currently configured target + std::vector getAffectedStacks(const Target & target) const override final; + + /// Returns list of target types that can be targeted by spell + std::vector getTargetTypes() const override final; + + /// Returns vector of all possible destinations for specified aim type + /// index - ??? + /// current - ??? + std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast) const override final; + + /// Returns true if spell can be cast on unit + bool isReceptive(const battle::Unit * target) const override; + + /// Returns list of hexes that are affected by spell assuming cast at centralHex + BattleHexArray rangeInHexes(BattleHex centralHex) const override; + + const Spell * getSpell() const override; + + bool counteringSelector(const Bonus * bonus) const; + +private: + std::shared_ptr effects; + std::shared_ptr targetCondition; + + std::vector affectedUnits; + effects::Effects::EffectsToApply effectsToApply; + + void beforeCast(BattleSpellCast & sc, vstd::RNG & rng, const Target & target); + + std::set collectTargets() const; + + void doRemoveEffects(ServerCallback * server, const std::vector & targets, const CSelector & selector); + + BattleHexArray spellRangeInHexes(BattleHex centralHex) const; + + Target transformSpellTarget(const Target & aimPoint) const; +}; + +} + + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index 4fac17a2d..84db51277 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -1,368 +1,368 @@ -/* - * ISpellMechanics.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include -#include - -#include "../battle/Destination.h" -#include "../int3.h" -#include "../GameConstants.h" -#include "../bonuses/Bonus.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct Query; -class IBattleState; -class CreatureService; -class CMap; -class CGameInfoCallback; -class CBattleInfoCallback; -class JsonNode; -class CStack; -class CGObjectInstance; -class CGHeroInstance; - -namespace spells -{ -class Service; -} - -namespace vstd -{ - class RNG; -} - -#if SCRIPTING_ENABLED -namespace scripting -{ - class Service; -} -#endif - - -///callback to be provided by server -class DLL_LINKAGE SpellCastEnvironment : public ServerCallback -{ -public: - virtual ~SpellCastEnvironment() = default; - - virtual const CMap * getMap() const = 0; - virtual const CGameInfoCallback * getCb() const = 0; - - virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; - virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove - - virtual void genericQuery(Query * request, PlayerColor color, std::function)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented -}; - -namespace spells -{ - -class DLL_LINKAGE IBattleCast -{ -public: - using Value = int32_t; - using Value64 = int64_t; - - using OptionalValue = std::optional; - using OptionalValue64 = std::optional; - - virtual const CSpell * getSpell() const = 0; - virtual Mode getMode() const = 0; - virtual const Caster * getCaster() const = 0; - virtual const CBattleInfoCallback * getBattle() const = 0; - - virtual OptionalValue getSpellLevel() const = 0; - - virtual OptionalValue getEffectPower() const = 0; - virtual OptionalValue getEffectDuration() const = 0; - - virtual OptionalValue64 getEffectValue() const = 0; - - virtual boost::logic::tribool isSmart() const = 0; - virtual boost::logic::tribool isMassive() const = 0; -}; - -///all parameters of particular cast event -class DLL_LINKAGE BattleCast : public IBattleCast -{ -public: - boost::logic::tribool smart; - boost::logic::tribool massive; - - //normal constructor - BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_); - - //magic mirror constructor - BattleCast(const BattleCast & orig, const Caster * caster_); - - virtual ~BattleCast(); - - ///IBattleCast - const CSpell * getSpell() const override; - Mode getMode() const override; - const Caster * getCaster() const override; - const CBattleInfoCallback * getBattle() const override; - - OptionalValue getSpellLevel() const override; - - OptionalValue getEffectPower() const override; - OptionalValue getEffectDuration() const override; - - OptionalValue64 getEffectValue() const override; - - boost::logic::tribool isSmart() const override; - boost::logic::tribool isMassive() const override; - - void setSpellLevel(Value value); - - void setEffectPower(Value value); - void setEffectDuration(Value value); - - void setEffectValue(Value64 value); - - ///only apply effects to specified targets - void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const; - - ///normal cast - void cast(ServerCallback * server, Target target); - - ///cast evaluation - void castEval(ServerCallback * server, Target target); - - ///cast with silent check for permitted cast - bool castIfPossible(ServerCallback * server, Target target); - - std::vector findPotentialTargets(bool fast = false) const; - -private: - ///spell school level - OptionalValue magicSkillLevel; - - ///actual spell-power affecting effect values - OptionalValue effectPower; - ///actual spell-power affecting effect duration - OptionalValue effectDuration; - - ///for Archangel-like casting - OptionalValue64 effectValue; - - Mode mode; - const CSpell * spell; - const CBattleInfoCallback * cb; - const Caster * caster; -}; - -class DLL_LINKAGE ISpellMechanicsFactory -{ -public: - virtual ~ISpellMechanicsFactory(); - - virtual std::unique_ptr create(const IBattleCast * event) const = 0; - - static std::unique_ptr get(const CSpell * s); - -protected: - const CSpell * spell; - - ISpellMechanicsFactory(const CSpell * s); -}; - -class DLL_LINKAGE Mechanics -{ -public: - virtual ~Mechanics(); - - virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0; - virtual bool adaptGenericProblem(Problem & target) const = 0; - - virtual BattleHexArray rangeInHexes(BattleHex centralHex) const = 0; - virtual std::vector getAffectedStacks(const Target & target) const = 0; - - virtual bool canBeCast(Problem & problem) const = 0; - virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0; - - virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0; - - virtual void cast(ServerCallback * server, const Target & target) = 0; - - virtual void castEval(ServerCallback * server, const Target & target) = 0; - - virtual bool isReceptive(const battle::Unit * target) const = 0; - - virtual std::vector getTargetTypes() const = 0; - - virtual std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0; - - virtual const Spell * getSpell() const = 0; - - //Cast event facade - - virtual IBattleCast::Value getEffectLevel() const = 0; - virtual IBattleCast::Value getRangeLevel() const = 0; - - virtual IBattleCast::Value getEffectPower() const = 0; - virtual IBattleCast::Value getEffectDuration() const = 0; - - virtual IBattleCast::Value64 getEffectValue() const = 0; - - virtual PlayerColor getCasterColor() const = 0; - - //Spell facade - virtual int32_t getSpellIndex() const = 0; - virtual SpellID getSpellId() const = 0; - virtual std::string getSpellName() const = 0; - virtual int32_t getSpellLevel() const = 0; - - virtual bool isSmart() const = 0; - virtual bool isMassive() const = 0; - virtual bool alwaysHitFirstTarget() const = 0; - virtual bool requiresClearTiles() const = 0; - - virtual bool isNegativeSpell() const = 0; - virtual bool isPositiveSpell() const = 0; - virtual bool isMagicalEffect() const = 0; - - virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0; - virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0; - virtual int64_t applySpecificSpellBonus(int64_t value) const = 0; - virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0; - - //Battle facade - virtual bool ownerMatches(const battle::Unit * unit) const = 0; - virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0; - - //Global environment facade - virtual const CreatureService * creatures() const = 0; -#if SCRIPTING_ENABLED - virtual const scripting::Service * scripts() const = 0; -#endif - virtual const Service * spells() const = 0; - - virtual const CBattleInfoCallback * battle() const = 0; - - const Caster * caster; - - BattleSide casterSide; - -protected: - Mechanics(); -}; - -class DLL_LINKAGE BaseMechanics : public Mechanics -{ -public: - virtual ~BaseMechanics(); - - bool adaptProblem(ESpellCastProblem source, Problem & target) const override; - bool adaptGenericProblem(Problem & target) const override; - - int32_t getSpellIndex() const override; - SpellID getSpellId() const override; - std::string getSpellName() const override; - int32_t getSpellLevel() const override; - - IBattleCast::Value getEffectLevel() const override; - IBattleCast::Value getRangeLevel() const override; - - IBattleCast::Value getEffectPower() const override; - IBattleCast::Value getEffectDuration() const override; - - IBattleCast::Value64 getEffectValue() const override; - - PlayerColor getCasterColor() const override; - - bool isSmart() const override; - bool isMassive() const override; - bool requiresClearTiles() const override; - bool alwaysHitFirstTarget() const override; - - bool isNegativeSpell() const override; - bool isPositiveSpell() const override; - bool isMagicalEffect() const override; - - int64_t adjustEffectValue(const battle::Unit * target) const override; - int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override; - int64_t applySpecificSpellBonus(int64_t value) const override; - int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override; - - bool ownerMatches(const battle::Unit * unit) const override; - bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override; - - std::vector getTargetTypes() const override; - - const CreatureService * creatures() const override; -#if SCRIPTING_ENABLED - const scripting::Service * scripts() const override; -#endif - const Service * spells() const override; - - const CBattleInfoCallback * battle() const override; - -protected: - const CSpell * owner; - Mode mode; - - BaseMechanics(const IBattleCast * event); - -private: - IBattleCast::Value rangeLevel; - IBattleCast::Value effectLevel; - - ///actual spell-power affecting effect values - IBattleCast::Value effectPower; - ///actual spell-power affecting effect duration - IBattleCast::Value effectDuration; - - ///raw damage/heal amount - IBattleCast::Value64 effectValue; - - boost::logic::tribool smart; - boost::logic::tribool massive; - - const CBattleInfoCallback * cb; -}; - -class DLL_LINKAGE IReceptiveCheck -{ -public: - virtual ~IReceptiveCheck() = default; - - virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0; -}; - -}// namespace spells - -class DLL_LINKAGE AdventureSpellCastParameters -{ -public: - const spells::Caster * caster; - int3 pos; -}; - -class DLL_LINKAGE IAdventureSpellMechanics -{ -public: - IAdventureSpellMechanics(const CSpell * s); - virtual ~IAdventureSpellMechanics() = default; - - virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0; - virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0; - - virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0; - - static std::unique_ptr createMechanics(const CSpell * s); -protected: - const CSpell * owner; -}; - -VCMI_LIB_NAMESPACE_END +/* + * ISpellMechanics.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include +#include + +#include "../battle/Destination.h" +#include "../int3.h" +#include "../GameConstants.h" +#include "../bonuses/Bonus.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct Query; +class IBattleState; +class CreatureService; +class CMap; +class CGameInfoCallback; +class CBattleInfoCallback; +class JsonNode; +class CStack; +class CGObjectInstance; +class CGHeroInstance; + +namespace spells +{ +class Service; +} + +namespace vstd +{ + class RNG; +} + +#if SCRIPTING_ENABLED +namespace scripting +{ + class Service; +} +#endif + + +///callback to be provided by server +class DLL_LINKAGE SpellCastEnvironment : public ServerCallback +{ +public: + virtual ~SpellCastEnvironment() = default; + + virtual const CMap * getMap() const = 0; + virtual const CGameInfoCallback * getCb() const = 0; + + virtual void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) = 0; + virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode mode) = 0; //TODO: remove + + virtual void genericQuery(Query * request, PlayerColor color, std::function)> callback) = 0;//TODO: type safety on query, use generic query packet when implemented +}; + +namespace spells +{ + +class DLL_LINKAGE IBattleCast +{ +public: + using Value = int32_t; + using Value64 = int64_t; + + using OptionalValue = std::optional; + using OptionalValue64 = std::optional; + + virtual const CSpell * getSpell() const = 0; + virtual Mode getMode() const = 0; + virtual const Caster * getCaster() const = 0; + virtual const CBattleInfoCallback * getBattle() const = 0; + + virtual OptionalValue getSpellLevel() const = 0; + + virtual OptionalValue getEffectPower() const = 0; + virtual OptionalValue getEffectDuration() const = 0; + + virtual OptionalValue64 getEffectValue() const = 0; + + virtual boost::logic::tribool isSmart() const = 0; + virtual boost::logic::tribool isMassive() const = 0; +}; + +///all parameters of particular cast event +class DLL_LINKAGE BattleCast : public IBattleCast +{ +public: + boost::logic::tribool smart; + boost::logic::tribool massive; + + //normal constructor + BattleCast(const CBattleInfoCallback * cb_, const Caster * caster_, const Mode mode_, const CSpell * spell_); + + //magic mirror constructor + BattleCast(const BattleCast & orig, const Caster * caster_); + + virtual ~BattleCast(); + + ///IBattleCast + const CSpell * getSpell() const override; + Mode getMode() const override; + const Caster * getCaster() const override; + const CBattleInfoCallback * getBattle() const override; + + OptionalValue getSpellLevel() const override; + + OptionalValue getEffectPower() const override; + OptionalValue getEffectDuration() const override; + + OptionalValue64 getEffectValue() const override; + + boost::logic::tribool isSmart() const override; + boost::logic::tribool isMassive() const override; + + void setSpellLevel(Value value); + + void setEffectPower(Value value); + void setEffectDuration(Value value); + + void setEffectValue(Value64 value); + + ///only apply effects to specified targets + void applyEffects(ServerCallback * server, const Target & target, bool indirect = false, bool ignoreImmunity = false) const; + + ///normal cast + void cast(ServerCallback * server, Target target); + + ///cast evaluation + void castEval(ServerCallback * server, Target target); + + ///cast with silent check for permitted cast + bool castIfPossible(ServerCallback * server, Target target); + + std::vector findPotentialTargets(bool fast = false) const; + +private: + ///spell school level + OptionalValue magicSkillLevel; + + ///actual spell-power affecting effect values + OptionalValue effectPower; + ///actual spell-power affecting effect duration + OptionalValue effectDuration; + + ///for Archangel-like casting + OptionalValue64 effectValue; + + Mode mode; + const CSpell * spell; + const CBattleInfoCallback * cb; + const Caster * caster; +}; + +class DLL_LINKAGE ISpellMechanicsFactory +{ +public: + virtual ~ISpellMechanicsFactory(); + + virtual std::unique_ptr create(const IBattleCast * event) const = 0; + + static std::unique_ptr get(const CSpell * s); + +protected: + const CSpell * spell; + + ISpellMechanicsFactory(const CSpell * s); +}; + +class DLL_LINKAGE Mechanics +{ +public: + virtual ~Mechanics(); + + virtual bool adaptProblem(ESpellCastProblem source, Problem & target) const = 0; + virtual bool adaptGenericProblem(Problem & target) const = 0; + + virtual BattleHexArray rangeInHexes(BattleHex centralHex) const = 0; + virtual std::vector getAffectedStacks(const Target & target) const = 0; + + virtual bool canBeCast(Problem & problem) const = 0; + virtual bool canBeCastAt(const Target & target, Problem & problem) const = 0; + + virtual void applyEffects(ServerCallback * server, const Target & targets, bool indirect, bool ignoreImmunity) const = 0; + + virtual void cast(ServerCallback * server, const Target & target) = 0; + + virtual void castEval(ServerCallback * server, const Target & target) = 0; + + virtual bool isReceptive(const battle::Unit * target) const = 0; + + virtual std::vector getTargetTypes() const = 0; + + virtual std::vector getPossibleDestinations(size_t index, AimType aimType, const Target & current, bool fast = false) const = 0; + + virtual const Spell * getSpell() const = 0; + + //Cast event facade + + virtual IBattleCast::Value getEffectLevel() const = 0; + virtual IBattleCast::Value getRangeLevel() const = 0; + + virtual IBattleCast::Value getEffectPower() const = 0; + virtual IBattleCast::Value getEffectDuration() const = 0; + + virtual IBattleCast::Value64 getEffectValue() const = 0; + + virtual PlayerColor getCasterColor() const = 0; + + //Spell facade + virtual int32_t getSpellIndex() const = 0; + virtual SpellID getSpellId() const = 0; + virtual std::string getSpellName() const = 0; + virtual int32_t getSpellLevel() const = 0; + + virtual bool isSmart() const = 0; + virtual bool isMassive() const = 0; + virtual bool alwaysHitFirstTarget() const = 0; + virtual bool requiresClearTiles() const = 0; + + virtual bool isNegativeSpell() const = 0; + virtual bool isPositiveSpell() const = 0; + virtual bool isMagicalEffect() const = 0; + + virtual int64_t adjustEffectValue(const battle::Unit * target) const = 0; + virtual int64_t applySpellBonus(int64_t value, const battle::Unit * target) const = 0; + virtual int64_t applySpecificSpellBonus(int64_t value) const = 0; + virtual int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const = 0; + + //Battle facade + virtual bool ownerMatches(const battle::Unit * unit) const = 0; + virtual bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const = 0; + + //Global environment facade + virtual const CreatureService * creatures() const = 0; +#if SCRIPTING_ENABLED + virtual const scripting::Service * scripts() const = 0; +#endif + virtual const Service * spells() const = 0; + + virtual const CBattleInfoCallback * battle() const = 0; + + const Caster * caster; + + BattleSide casterSide; + +protected: + Mechanics(); +}; + +class DLL_LINKAGE BaseMechanics : public Mechanics +{ +public: + virtual ~BaseMechanics(); + + bool adaptProblem(ESpellCastProblem source, Problem & target) const override; + bool adaptGenericProblem(Problem & target) const override; + + int32_t getSpellIndex() const override; + SpellID getSpellId() const override; + std::string getSpellName() const override; + int32_t getSpellLevel() const override; + + IBattleCast::Value getEffectLevel() const override; + IBattleCast::Value getRangeLevel() const override; + + IBattleCast::Value getEffectPower() const override; + IBattleCast::Value getEffectDuration() const override; + + IBattleCast::Value64 getEffectValue() const override; + + PlayerColor getCasterColor() const override; + + bool isSmart() const override; + bool isMassive() const override; + bool requiresClearTiles() const override; + bool alwaysHitFirstTarget() const override; + + bool isNegativeSpell() const override; + bool isPositiveSpell() const override; + bool isMagicalEffect() const override; + + int64_t adjustEffectValue(const battle::Unit * target) const override; + int64_t applySpellBonus(int64_t value, const battle::Unit * target) const override; + int64_t applySpecificSpellBonus(int64_t value) const override; + int64_t calculateRawEffectValue(int32_t basePowerMultiplier, int32_t levelPowerMultiplier) const override; + + bool ownerMatches(const battle::Unit * unit) const override; + bool ownerMatches(const battle::Unit * unit, const boost::logic::tribool positivness) const override; + + std::vector getTargetTypes() const override; + + const CreatureService * creatures() const override; +#if SCRIPTING_ENABLED + const scripting::Service * scripts() const override; +#endif + const Service * spells() const override; + + const CBattleInfoCallback * battle() const override; + +protected: + const CSpell * owner; + Mode mode; + + BaseMechanics(const IBattleCast * event); + +private: + IBattleCast::Value rangeLevel; + IBattleCast::Value effectLevel; + + ///actual spell-power affecting effect values + IBattleCast::Value effectPower; + ///actual spell-power affecting effect duration + IBattleCast::Value effectDuration; + + ///raw damage/heal amount + IBattleCast::Value64 effectValue; + + boost::logic::tribool smart; + boost::logic::tribool massive; + + const CBattleInfoCallback * cb; +}; + +class DLL_LINKAGE IReceptiveCheck +{ +public: + virtual ~IReceptiveCheck() = default; + + virtual bool isReceptive(const Mechanics * m, const battle::Unit * target) const = 0; +}; + +}// namespace spells + +class DLL_LINKAGE AdventureSpellCastParameters +{ +public: + const spells::Caster * caster; + int3 pos; +}; + +class DLL_LINKAGE IAdventureSpellMechanics +{ +public: + IAdventureSpellMechanics(const CSpell * s); + virtual ~IAdventureSpellMechanics() = default; + + virtual bool canBeCast(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster) const = 0; + virtual bool canBeCastAt(spells::Problem & problem, const CGameInfoCallback * cb, const spells::Caster * caster, const int3 & pos) const = 0; + + virtual bool adventureCast(SpellCastEnvironment * env, const AdventureSpellCastParameters & parameters) const = 0; + + static std::unique_ptr createMechanics(const CSpell * s); +protected: + const CSpell * owner; +}; + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Effect.h b/lib/spells/effects/Effect.h index f20af46c3..8cb5f446c 100644 --- a/lib/spells/effects/Effect.h +++ b/lib/spells/effects/Effect.h @@ -1,83 +1,83 @@ -/* - * Effect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include - -VCMI_LIB_NAMESPACE_BEGIN - -struct BattleHex; -class BattleHexArray; -class CBattleInfoCallback; -class JsonSerializeFormat; -class ServerCallback; - -namespace vstd -{ - class RNG; -} - -namespace spells -{ -using EffectTarget = Target; - -namespace effects -{ -using RNG = vstd::RNG; -class Effects; -class Effect; -class Registry; - -using TargetType = spells::AimType; - -class DLL_LINKAGE Effect -{ -public: - bool indirect = false; - bool optional = false; - - std::string name; - - virtual ~Effect() = default; //Required for child classes - - // TODO: document me - virtual void adjustTargetTypes(std::vector & types) const = 0; - - /// Generates list of hexes affected by spell, if spell were to cast at specified target - virtual void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const = 0; - - /// Returns whether effect has any valid targets on the battlefield - virtual bool applicable(Problem & problem, const Mechanics * m) const; - - /// Returns whether effect is valid and can be applied onto selected target - virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const; - - virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0; - - /// Processes input target and generates subset-result that contains only valid targets - virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0; - - // TODO: document me - virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0; - - /// Serializes (or deserializes) parameters of Effect - void serializeJson(JsonSerializeFormat & handler); - - static std::shared_ptr create(const Registry * registry, const std::string & type); - -protected: - virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Effect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include + +VCMI_LIB_NAMESPACE_BEGIN + +struct BattleHex; +class BattleHexArray; +class CBattleInfoCallback; +class JsonSerializeFormat; +class ServerCallback; + +namespace vstd +{ + class RNG; +} + +namespace spells +{ +using EffectTarget = Target; + +namespace effects +{ +using RNG = vstd::RNG; +class Effects; +class Effect; +class Registry; + +using TargetType = spells::AimType; + +class DLL_LINKAGE Effect +{ +public: + bool indirect = false; + bool optional = false; + + std::string name; + + virtual ~Effect() = default; //Required for child classes + + // TODO: document me + virtual void adjustTargetTypes(std::vector & types) const = 0; + + /// Generates list of hexes affected by spell, if spell were to cast at specified target + virtual void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const = 0; + + /// Returns whether effect has any valid targets on the battlefield + virtual bool applicable(Problem & problem, const Mechanics * m) const; + + /// Returns whether effect is valid and can be applied onto selected target + virtual bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const; + + virtual void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const = 0; + + /// Processes input target and generates subset-result that contains only valid targets + virtual EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const = 0; + + // TODO: document me + virtual EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const = 0; + + /// Serializes (or deserializes) parameters of Effect + void serializeJson(JsonSerializeFormat & handler); + + static std::shared_ptr create(const Registry * registry, const std::string & type); + +protected: + virtual void serializeJsonEffect(JsonSerializeFormat & handler) = 0; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/LocationEffect.cpp b/lib/spells/effects/LocationEffect.cpp index 82ed0f54c..a1b1c3419 100644 --- a/lib/spells/effects/LocationEffect.cpp +++ b/lib/spells/effects/LocationEffect.cpp @@ -1,52 +1,52 @@ -/* - * LocationEffect.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#include "StdInc.h" - -#include "LocationEffect.h" -#include "../ISpellMechanics.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -void LocationEffect::adjustTargetTypes(std::vector & types) const -{ - -} - -void LocationEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const -{ - for(const auto & destnation : spellTarget) - hexes.insert(destnation.hexValue); -} - -EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const -{ - EffectTarget res; - vstd::copy_if(target, std::back_inserter(res), [](const Destination & d) - { - return !d.unitValue && (d.hexValue.isValid()); - }); - return res; -} - -EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const -{ - //by default effect covers exactly spell range - return EffectTarget(spellTarget); -} - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * LocationEffect.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include "LocationEffect.h" +#include "../ISpellMechanics.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +void LocationEffect::adjustTargetTypes(std::vector & types) const +{ + +} + +void LocationEffect::adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const +{ + for(const auto & destnation : spellTarget) + hexes.insert(destnation.hexValue); +} + +EffectTarget LocationEffect::filterTarget(const Mechanics * m, const EffectTarget & target) const +{ + EffectTarget res; + vstd::copy_if(target, std::back_inserter(res), [](const Destination & d) + { + return !d.unitValue && (d.hexValue.isValid()); + }); + return res; +} + +EffectTarget LocationEffect::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const +{ + //by default effect covers exactly spell range + return EffectTarget(spellTarget); +} + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/LocationEffect.h b/lib/spells/effects/LocationEffect.h index a47c7e608..8a99d163b 100644 --- a/lib/spells/effects/LocationEffect.h +++ b/lib/spells/effects/LocationEffect.h @@ -1,41 +1,41 @@ -/* - * LocationEffect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" - -VCMI_LIB_NAMESPACE_BEGIN - - -namespace spells -{ -namespace effects -{ - -class LocationEffect : public Effect -{ -public: - void adjustTargetTypes(std::vector & types) const override; - - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; -protected: - -private: -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * LocationEffect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" + +VCMI_LIB_NAMESPACE_BEGIN + + +namespace spells +{ +namespace effects +{ + +class LocationEffect : public Effect +{ +public: + void adjustTargetTypes(std::vector & types) const override; + + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; +protected: + +private: +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Moat.h b/lib/spells/effects/Moat.h index 4e0c020f8..ac940fc7d 100644 --- a/lib/spells/effects/Moat.h +++ b/lib/spells/effects/Moat.h @@ -1,43 +1,43 @@ -/* - * Moat.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Obstacle.h" - -VCMI_LIB_NAMESPACE_BEGIN - -struct Bonus; - -namespace spells -{ -namespace effects -{ - -class Moat : public Obstacle -{ -private: - ObstacleSideOptions sideOptions; //Defender only - std::vector moatHexes; //Determine number of moat patches and hexes - std::vector> bonus; //For battle-wide bonuses - bool dispellable; //For Tower landmines - int moatDamage; // Minimal moat damage -public: - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override; - void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - void convertBonus(const Mechanics * m, std::vector & converted) const; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Moat.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Obstacle.h" + +VCMI_LIB_NAMESPACE_BEGIN + +struct Bonus; + +namespace spells +{ +namespace effects +{ + +class Moat : public Obstacle +{ +private: + ObstacleSideOptions sideOptions; //Defender only + std::vector moatHexes; //Determine number of moat patches and hexes + std::vector> bonus; //For battle-wide bonuses + bool dispellable; //For Tower landmines + int moatDamage; // Minimal moat damage +public: + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override; + void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + void convertBonus(const Mechanics * m, std::vector & converted) const; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Obstacle.h b/lib/spells/effects/Obstacle.h index 7677d4523..2c3624669 100644 --- a/lib/spells/effects/Obstacle.h +++ b/lib/spells/effects/Obstacle.h @@ -1,78 +1,78 @@ -/* - * Obstacle.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "LocationEffect.h" -#include "../../GameConstants.h" -#include "../../battle/BattleHexArray.h" -#include "../../battle/CObstacleInstance.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class ObstacleSideOptions -{ -public: - using RelativeShape = std::vector>; - - RelativeShape shape; //shape of single obstacle relative to obstacle position - RelativeShape range; //position of obstacles relative to effect destination - - AudioPath appearSound; - AnimationPath appearAnimation; - AnimationPath animation; - - int offsetY = 0; - - void serializeJson(JsonSerializeFormat & handler); -}; - -class Obstacle : public LocationEffect -{ -public: - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override; - virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const; - - bool hidden = false; - bool trigger = false; - bool trap = false; - bool removeOnTrigger = false; - bool hideNative = false; - SpellID triggerAbility; -private: - int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine) - bool passable = false; - int32_t turnsRemaining = -1; - - BattleSideArray sideOptions; - - static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear); - static bool noRoomToPlace(Problem & problem, const Mechanics * m); -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Obstacle.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "LocationEffect.h" +#include "../../GameConstants.h" +#include "../../battle/BattleHexArray.h" +#include "../../battle/CObstacleInstance.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class ObstacleSideOptions +{ +public: + using RelativeShape = std::vector>; + + RelativeShape shape; //shape of single obstacle relative to obstacle position + RelativeShape range; //position of obstacles relative to effect destination + + AudioPath appearSound; + AnimationPath appearAnimation; + AnimationPath animation; + + int offsetY = 0; + + void serializeJson(JsonSerializeFormat & handler); +}; + +class Obstacle : public LocationEffect +{ +public: + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override; + virtual void placeObstacles(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const; + + bool hidden = false; + bool trigger = false; + bool trap = false; + bool removeOnTrigger = false; + bool hideNative = false; + SpellID triggerAbility; +private: + int32_t patchCount = 0; //random patches to place, for massive spells should be >= 1, for non-massive ones if >= 1, then place only this number inside a target (like H5 landMine) + bool passable = false; + int32_t turnsRemaining = -1; + + BattleSideArray sideOptions; + + static bool isHexAvailable(const CBattleInfoCallback * cb, const BattleHex & hex, const bool mustBeClear); + static bool noRoomToPlace(Problem & problem, const Mechanics * m); +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/Summon.h b/lib/spells/effects/Summon.h index 55b09fc47..dce5e1c4a 100644 --- a/lib/spells/effects/Summon.h +++ b/lib/spells/effects/Summon.h @@ -1,55 +1,55 @@ -/* - * Summon.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" -#include "../../GameConstants.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class Summon : public Effect -{ -public: - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - void adjustTargetTypes(std::vector & types) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - - void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - -protected: - void serializeJsonEffect(JsonSerializeFormat & handler) override final; - -private: - int32_t summonedCreatureAmount(const Mechanics * m) const; - int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const; - - CreatureID creature; - - bool permanent = false; - bool exclusive = true; - bool summonByHealth = false; - bool summonSameUnit = false; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * Summon.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" +#include "../../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class Summon : public Effect +{ +public: + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + void adjustTargetTypes(std::vector & types) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + + void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + +protected: + void serializeJsonEffect(JsonSerializeFormat & handler) override final; + +private: + int32_t summonedCreatureAmount(const Mechanics * m) const; + int32_t summonedCreatureHealth(const Mechanics * m, const battle::Unit * unit) const; + + CreatureID creature; + + bool permanent = false; + bool exclusive = true; + bool summonByHealth = false; + bool summonSameUnit = false; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/spells/effects/UnitEffect.h b/lib/spells/effects/UnitEffect.h index 521e5ab0a..de2b481eb 100644 --- a/lib/spells/effects/UnitEffect.h +++ b/lib/spells/effects/UnitEffect.h @@ -1,61 +1,61 @@ -/* - * UnitEffect.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#pragma once - -#include "Effect.h" - -VCMI_LIB_NAMESPACE_BEGIN - -namespace spells -{ -namespace effects -{ - -class UnitEffect : public Effect -{ -public: - void adjustTargetTypes(std::vector & types) const override; - - void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; - - bool applicable(Problem & problem, const Mechanics * m) const override; - bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; - - EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; - - bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const; - - virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const; - -protected: - int32_t chainLength = 0; - double chainFactor = 0.0; - - virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const; - virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const; - virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const; - - void serializeJsonEffect(JsonSerializeFormat & handler) override final; - virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0; - -private: - bool ignoreImmunity = false; - - EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; - EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; -}; - -} -} - -VCMI_LIB_NAMESPACE_END +/* + * UnitEffect.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "Effect.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace spells +{ +namespace effects +{ + +class UnitEffect : public Effect +{ +public: + void adjustTargetTypes(std::vector & types) const override; + + void adjustAffectedHexes(BattleHexArray & hexes, const Mechanics * m, const Target & spellTarget) const override; + + bool applicable(Problem & problem, const Mechanics * m) const override; + bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget filterTarget(const Mechanics * m, const EffectTarget & target) const override; + + EffectTarget transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const override; + + bool getStackFilter(const Mechanics * m, bool alwaysSmart, const battle::Unit * s) const; + + virtual bool eraseByImmunityFilter(const Mechanics * m, const battle::Unit * s) const; + +protected: + int32_t chainLength = 0; + double chainFactor = 0.0; + + virtual bool isReceptive(const Mechanics * m, const battle::Unit * unit) const; + virtual bool isSmartTarget(const Mechanics * m, const battle::Unit * unit, bool alwaysSmart) const; + virtual bool isValidTarget(const Mechanics * m, const battle::Unit * unit) const; + + void serializeJsonEffect(JsonSerializeFormat & handler) override final; + virtual void serializeJsonUnitEffect(JsonSerializeFormat & handler) = 0; + +private: + bool ignoreImmunity = false; + + EffectTarget transformTargetByRange(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; + EffectTarget transformTargetByChain(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const; +}; + +} +} + +VCMI_LIB_NAMESPACE_END diff --git a/server/battles/BattleFlowProcessor.h b/server/battles/BattleFlowProcessor.h index 6982f739d..b628bbc6a 100644 --- a/server/battles/BattleFlowProcessor.h +++ b/server/battles/BattleFlowProcessor.h @@ -1,62 +1,62 @@ -/* - * BattleFlowProcessor.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ -#pragma once - -#include "../lib/battle/BattleSide.h" - -VCMI_LIB_NAMESPACE_BEGIN -class CStack; -struct BattleHex; -class BattleHexArray; -class BattleAction; -class CBattleInfoCallback; -struct CObstacleInstance; -namespace battle -{ -class Unit; -} -VCMI_LIB_NAMESPACE_END - -class CGameHandler; -class BattleProcessor; - -/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions -class BattleFlowProcessor : boost::noncopyable -{ - BattleProcessor * owner; - CGameHandler * gameHandler; - - const CStack * getNextStack(const CBattleInfoCallback & battle); - - bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack); - bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack); - - void summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex); - void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack); - void tryPlaceMoats(const CBattleInfoCallback & battle); - void castOpeningSpells(const CBattleInfoCallback & battle); - void activateNextStack(const CBattleInfoCallback & battle); - void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound); - - void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack); - void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle); - void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack); - void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack); - - void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next); - bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) - -public: - explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler); - - void onBattleStarted(const CBattleInfoCallback & battle); - void onTacticsEnded(const CBattleInfoCallback & battle); - void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba); -}; +/* + * BattleFlowProcessor.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +#include "../lib/battle/BattleSide.h" + +VCMI_LIB_NAMESPACE_BEGIN +class CStack; +struct BattleHex; +class BattleHexArray; +class BattleAction; +class CBattleInfoCallback; +struct CObstacleInstance; +namespace battle +{ +class Unit; +} +VCMI_LIB_NAMESPACE_END + +class CGameHandler; +class BattleProcessor; + +/// Controls flow of battles - battle startup actions and switching to next stack or next round after actions +class BattleFlowProcessor : boost::noncopyable +{ + BattleProcessor * owner; + CGameHandler * gameHandler; + + const CStack * getNextStack(const CBattleInfoCallback & battle); + + bool rollGoodMorale(const CBattleInfoCallback & battle, const CStack * stack); + bool tryMakeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack); + + void summonGuardiansHelper(const CBattleInfoCallback & battle, BattleHexArray & output, const BattleHex & targetPosition, BattleSide side, bool targetIsTwoHex); + void trySummonGuardians(const CBattleInfoCallback & battle, const CStack * stack); + void tryPlaceMoats(const CBattleInfoCallback & battle); + void castOpeningSpells(const CBattleInfoCallback & battle); + void activateNextStack(const CBattleInfoCallback & battle); + void startNextRound(const CBattleInfoCallback & battle, bool isFirstRound); + + void stackEnchantedTrigger(const CBattleInfoCallback & battle, const CStack * stack); + void removeObstacle(const CBattleInfoCallback & battle, const CObstacleInstance & obstacle); + void stackTurnTrigger(const CBattleInfoCallback & battle, const CStack * stack); + void setActiveStack(const CBattleInfoCallback & battle, const battle::Unit * stack); + + void makeStackDoNothing(const CBattleInfoCallback & battle, const CStack * next); + bool makeAutomaticAction(const CBattleInfoCallback & battle, const CStack * stack, BattleAction & ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) + +public: + explicit BattleFlowProcessor(BattleProcessor * owner, CGameHandler * newGameHandler); + + void onBattleStarted(const CBattleInfoCallback & battle); + void onTacticsEnded(const CBattleInfoCallback & battle); + void onActionMade(const CBattleInfoCallback & battle, const BattleAction & ba); +};