1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Nullkiller: update / fix build, core changes required for Nullkiller AI

This commit is contained in:
Andrii Danylchenko 2021-05-16 20:53:11 +03:00 committed by Andrii Danylchenko
parent b4241670ba
commit 3fa7e0976f
28 changed files with 379 additions and 233 deletions

View File

@ -167,11 +167,22 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
if(bestSpellcast.is_initialized() && bestSpellcast->value > bestAttack.damageDiff())
return BattleAction::makeCreatureSpellcast(stack, bestSpellcast->dest, bestSpellcast->spell->id);
else if(bestAttack.attack.shooting)
{
auto &target = bestAttack;
logAi->debug("BattleAI: %s -> %s x %d, shot, from %d curpos %d dist %d speed %d: %lld %lld %lld",
target.attackerState->unitType()->identifier,
target.affectedUnits[0]->unitType()->identifier,
(int)target.affectedUnits.size(), (int)target.from, (int)bestAttack.attack.attacker->getPosition().hex,
bestAttack.attack.chargedFields, bestAttack.attack.attacker->Speed(0, true),
target.damageDealt, target.damageReceived, target.attackValue()
);
return BattleAction::makeShotAttack(stack, bestAttack.attack.defender);
}
else
{
auto &target = bestAttack;
logAi->debug("BattleAI: %s -> %s %d from, %d curpos %d dist %d speed %d: %lld %lld %lld",
logAi->debug("BattleAI: %s -> %s x %d, mellee, from %d curpos %d dist %d speed %d: %lld %lld %lld",
target.attackerState->unitType()->identifier,
target.affectedUnits[0]->unitType()->identifier,
(int)target.affectedUnits.size(), (int)target.from, (int)bestAttack.attack.attacker->getPosition().hex,

View File

@ -146,13 +146,16 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
auto morale = slot.second->MoraleVal();
auto multiplier = 1.0f;
const float BadMoraleChance = 0.083f;
const float HighMoraleChance = 0.04f;
if(morale < 0)
{
multiplier += morale * 0.083f;
multiplier += morale * BadMoraleChance;
}
else if(morale > 0)
{
multiplier += morale * 0.04f;
multiplier += morale * HighMoraleChance;
}
newValue += multiplier * slot.second->getPower();
@ -439,7 +442,7 @@ std::vector<StackUpgradeInfo> ArmyManager::getPossibleUpgrades(const CCreatureSe
return upgrades;
}
ArmyUpgradeInfo ArmyManager::calculateCreateresUpgrade(
ArmyUpgradeInfo ArmyManager::calculateCreaturesUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const

View File

@ -55,7 +55,7 @@ public:
virtual std::vector<creInfo> getArmyAvailableToBuy(const CCreatureSet * hero, const CGDwelling * dwelling, TResources availableRes) const = 0;
virtual uint64_t evaluateStackPower(const CCreature * creature, int count) const = 0;
virtual SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const = 0;
virtual ArmyUpgradeInfo calculateCreateresUpgrade(
virtual ArmyUpgradeInfo calculateCreaturesUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const = 0;
@ -90,7 +90,7 @@ public:
std::shared_ptr<CCreatureSet> getArmyAvailableToBuyAsCCreatureSet(const CGDwelling * dwelling, TResources availableRes) const override;
uint64_t evaluateStackPower(const CCreature * creature, int count) const override;
SlotInfo getTotalCreaturesAvailable(CreatureID creatureID) const override;
ArmyUpgradeInfo calculateCreateresUpgrade(
ArmyUpgradeInfo calculateCreaturesUpgrade(
const CCreatureSet * army,
const CGObjectInstance * upgrader,
const TResources & availableResources) const override;

View File

@ -224,7 +224,7 @@ Goals::TGoalVec GatherArmyBehavior::upgradeArmy(const CGTownInstance * upgrader)
continue;
}
auto upgrade = ai->nullkiller->armyManager->calculateCreateresUpgrade(path.heroArmy, upgrader, availableResources);
auto upgrade = ai->nullkiller->armyManager->calculateCreaturesUpgrade(path.heroArmy, upgrader, availableResources);
auto armyValue = (float)upgrade.upgradeValue / path.getHeroStrength();
if(armyValue < 0.1f || upgrade.upgradeValue < 300) // avoid small upgrades

View File

@ -95,8 +95,16 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
auto resources = bankInfo->getPossibleResourcesReward();
TResources result = TResources();
int sum = 0;
return resources;
for(auto & reward : resources)
{
result += reward.data * reward.chance;
sum += reward.chance;
}
return sum > 1 ? result / sum : result;
}
uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
@ -108,7 +116,7 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
for(auto c : creatures)
{
result += c.type->AIValue * c.count;
result += c.data.type->AIValue * c.data.count * c.chance / 100;
}
return result;
@ -205,7 +213,7 @@ uint64_t RewardEvaluator::getArmyReward(
case Obj::TOWN:
return target->tempOwner == PlayerColor::NEUTRAL ? 1000 : 10000;
case Obj::HILL_FORT:
return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
case Obj::CREATURE_BANK:
return getCreatureBankArmyReward(target, hero);
case Obj::CREATURE_GENERATOR1:
@ -239,7 +247,7 @@ int RewardEvaluator::getGoldCost(const CGObjectInstance * target, const CGHeroIn
switch(target->ID)
{
case Obj::HILL_FORT:
return ai->armyManager->calculateCreateresUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD];
return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeCost[Res::GOLD];
case Obj::SCHOOL_OF_MAGIC:
case Obj::SCHOOL_OF_WAR:
return 1000;

View File

@ -1267,7 +1267,7 @@ std::vector<AIPath> AINodeStorage::getChainInfo(const int3 & pos, bool isOnLand)
path.targetHero = node.actor->hero;
path.heroArmy = node.actor->creatureSet;
path.armyLoss = node.armyLoss;
path.targetObjectDanger = evaluateDanger(pos, path.targetHero, false);
path.targetObjectDanger = evaluateDanger(pos, path.targetHero, !node.actor->allowBattle);
path.targetObjectArmyLoss = evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
path.chainMask = node.actor->chainMask;
path.exchangeCount = node.actor->actorExchangeCount;

View File

@ -11,7 +11,7 @@
#pragma once
#define PATHFINDER_TRACE_LEVEL 0
#define AI_TRACE_LEVEL 1
#define AI_TRACE_LEVEL 0
#define SCOUT_TURN_DISTANCE_LIMIT 3
#define MAIN_TURN_DISTANCE_LIMIT 5

View File

@ -343,7 +343,7 @@ HeroExchangeArmy * HeroExchangeMap::tryUpgrade(
TResources resources) const
{
HeroExchangeArmy * target = new HeroExchangeArmy();
auto upgradeInfo = ai->armyManager->calculateCreateresUpgrade(army, upgrader, resources);
auto upgradeInfo = ai->armyManager->calculateCreaturesUpgrade(army, upgrader, resources);
if(upgradeInfo.upgradeValue)
{

View File

@ -26,7 +26,7 @@ AINodeStorage::AINodeStorage(const int3 & Sizes)
AINodeStorage::~AINodeStorage() = default;
void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
void AINodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
{
//TODO: fix this code duplication with NodeStorage::initialize, problem is to keep `resetTile` inline
@ -109,7 +109,7 @@ boost::optional<AIPathNode *> AINodeStorage::getOrCreateNode(const int3 & pos, c
return boost::none;
}
CGPathNode * AINodeStorage::getInitialNode()
std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
{
auto hpos = hero->getPosition(false);
auto initialNode =
@ -121,7 +121,7 @@ CGPathNode * AINodeStorage::getInitialNode()
initialNode->danger = 0;
initialNode->setCost(0.0);
return initialNode;
return {initialNode};
}
void AINodeStorage::resetTile(const int3 & coord, EPathfindingLayer layer, CGPathNode::EAccessibility accessibility)

View File

@ -80,9 +80,9 @@ public:
AINodeStorage(const int3 & sizes);
~AINodeStorage();
void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
void initialize(const PathfinderOptions & options, const CGameState * gs) override;
virtual CGPathNode * getInitialNode() override;
virtual std::vector<CGPathNode *> getInitialNodes() override;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
@ -106,11 +106,7 @@ public:
bool isTileAccessible(const int3 & pos, const EPathfindingLayer layer) const;
void setHero(HeroPtr heroPtr, const VCAI * ai);
const CGHeroInstance * getHero() const
{
return hero;
}
const CGHeroInstance * getHero() const { return hero; }
uint64_t evaluateDanger(const int3 & tile) const
{

View File

@ -56,8 +56,8 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr<AIPathfinding::AIPathfinderConfig> config)
{
logAi->debug("Recalculate paths for %s", hero->name);
cb->calculatePaths(config, hero);
cb->calculatePaths(config);
};
std::vector<CThreadHelper::Task> calculationTasks;

View File

@ -37,7 +37,17 @@ namespace AIPathfinding
CPlayerSpecificInfoCallback * cb,
VCAI * ai,
std::shared_ptr<AINodeStorage> nodeStorage)
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage))
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
{
}
CPathfinderHelper * AIPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
{
if(!helper)
{
helper.reset(new CPathfinderHelper(gs, hero, options));
}
return helper.get();
}
}

View File

@ -17,10 +17,16 @@ namespace AIPathfinding
{
class AIPathfinderConfig : public PathfinderConfig
{
private:
const CGHeroInstance * hero;
std::unique_ptr<CPathfinderHelper> helper;
public:
AIPathfinderConfig(
CPlayerSpecificInfoCallback * cb,
VCAI * ai,
std::shared_ptr<AINodeStorage> nodeStorage);
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
};
}

View File

@ -2670,10 +2670,16 @@ void AIStatus::attemptedAnsweringQuery(QueryID queryID, int answerRequestID)
void AIStatus::receivedAnswerConfirmation(int answerRequestID, int result)
{
assert(vstd::contains(requestToQueryID, answerRequestID));
QueryID query = requestToQueryID[answerRequestID];
assert(vstd::contains(remainingQueries, query));
requestToQueryID.erase(answerRequestID);
QueryID query;
{
boost::unique_lock<boost::mutex> lock(mx);
assert(vstd::contains(requestToQueryID, answerRequestID));
query = requestToQueryID[answerRequestID];
assert(vstd::contains(remainingQueries, query));
requestToQueryID.erase(answerRequestID);
}
if(result)
{

View File

@ -4,7 +4,7 @@ git submodule update --init --recursive
cd ..
curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" \
"https://github.com/nullkiller/vcmi-deps-windows/releases/download/v1.3/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.3/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
rmdir vcpkg\installed\${VCMI_BUILD_PLATFORM}-windows\debug /S/Q

View File

@ -15,7 +15,7 @@ sudo apt-get install -f --yes
if false; then
# Add MXE repository and key
echo "deb http://pkg.mxe.cc/repos/apt trusty main" \
echo "deb http://pkg.mxe.cc/repos/apt/debian wheezy main" \
| sudo tee /etc/apt/sources.list.d/mxeapt.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D43A795B73B16ABE9643FE1AFD8FFF16DB45C6AB
@ -35,6 +35,7 @@ if false; then
mxe-$MXE_TARGET-ffmpeg \
mxe-$MXE_TARGET-qt \
mxe-$MXE_TARGET-qtbase \
mxe-$MXE_TARGET-intel-tbb \
mxe-i686-w64-mingw32.static-luajit
fi # Disable

View File

@ -159,7 +159,7 @@ if(ENABLE_DEBUG_CONSOLE)
else()
add_executable(vcmiclient WIN32 ${client_SRCS} ${client_HEADERS} ${client_ICON})
endif(ENABLE_DEBUG_CONSOLE)
add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI)
add_dependencies(vcmiclient vcmiserver BattleAI StupidAI VCAI Nullkiller)
if(WIN32)
set_target_properties(vcmiclient

View File

@ -923,9 +923,9 @@ void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3, ShashInt
gs->getTilesInRange(tiles, pos, radious, getLocalPlayer(), -1, distanceFormula);
}
void CGameInfoCallback::calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero)
void CGameInfoCallback::calculatePaths(std::shared_ptr<PathfinderConfig> config)
{
gs->calculatePaths(config, hero);
gs->calculatePaths(config);
}
const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const

View File

@ -188,7 +188,7 @@ public:
virtual std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
virtual bool isInTheMap(const int3 &pos) const;
virtual void getVisibleTilesInRange(std::unordered_set<int3, ShashInt3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
virtual void calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero);
virtual void calculatePaths(std::shared_ptr<PathfinderConfig> config);
//town
virtual const CGTownInstance* getTown(ObjectInstanceID objid) const;

View File

@ -2056,9 +2056,9 @@ void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
pathfinder.calculatePaths();
}
void CGameState::calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero)
void CGameState::calculatePaths(std::shared_ptr<PathfinderConfig> config)
{
CPathfinder pathfinder(this, hero, config);
CPathfinder pathfinder(this, config);
pathfinder.calculatePaths();
}

View File

@ -184,7 +184,7 @@ public:
PlayerRelations::PlayerRelations getPlayerRelations(PlayerColor color1, PlayerColor color2);
bool checkForVisitableDir(const int3 & src, const int3 & dst) const; //check if src tile is visitable from dst tile
void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
void calculatePaths(std::shared_ptr<PathfinderConfig> config, const CGHeroInstance * hero) override;
void calculatePaths(std::shared_ptr<PathfinderConfig> config) override;
int3 guardingCreaturePosition (int3 pos) const;
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
void updateRumor();

View File

@ -26,14 +26,14 @@ bool canSeeObj(const CGObjectInstance * obj)
return obj != nullptr && obj->ID != Obj::EVENT;
}
void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero)
void NodeStorage::initialize(const PathfinderOptions & options, const CGameState * gs)
{
//TODO: fix this code duplication with AINodeStorage::initialize, problem is to keep `resetTile` inline
int3 pos;
const PlayerColor player = out.hero->tempOwner;
const int3 sizes = gs->getMapSize();
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(hero->tempOwner)->fogOfWarMap;
const PlayerColor player = hero->tempOwner;
const auto & fow = static_cast<const CGameInfoCallback *>(gs)->getPlayerTeam(player)->fogOfWarMap;
//make 200% sure that these are loop invariants (also a bit shorter code), let compiler do the rest(loop unswitching)
const bool useFlying = options.useFlying;
@ -155,15 +155,20 @@ void NodeStorage::resetTile(
getNode(tile, layer)->update(tile, layer, accessibility);
}
CGPathNode * NodeStorage::getInitialNode()
std::vector<CGPathNode *> NodeStorage::getInitialNodes()
{
auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
auto initialNode = getNode(out.hpos, out.hero->boat ? EPathfindingLayer::SAIL : EPathfindingLayer::LAND);
initialNode->turns = 0;
initialNode->moveRemains = out.hero->movement;
initialNode->setCost(0.0);
return initialNode;
if(!initialNode->coord.valid())
{
initialNode->coord = out.hpos;
}
return std::vector<CGPathNode *> { initialNode };
}
void NodeStorage::commit(CDestinationNodeInfo & destination, const PathNodeInfo & source)
@ -256,36 +261,40 @@ CPathfinder::CPathfinder(
const CGHeroInstance * _hero)
: CPathfinder(
_gs,
_hero,
std::make_shared<PathfinderConfig>(
std::make_shared<NodeStorage>(_out, _hero),
std::vector<std::shared_ptr<IPathfindingRule>>{
std::make_shared<LayerTransitionRule>(),
std::make_shared<DestinationActionRule>(),
std::make_shared<MovementToDestinationRule>(),
std::make_shared<MovementCostRule>(),
std::make_shared<MovementAfterDestinationRule>()
}))
std::make_shared<SingleHeroPathfinderConfig>(_out, _gs, _hero))
{
}
std::vector<std::shared_ptr<IPathfindingRule>> SingleHeroPathfinderConfig::buildRuleSet()
{
return std::vector<std::shared_ptr<IPathfindingRule>>{
std::make_shared<LayerTransitionRule>(),
std::make_shared<DestinationActionRule>(),
std::make_shared<MovementToDestinationRule>(),
std::make_shared<MovementCostRule>(),
std::make_shared<MovementAfterDestinationRule>()
};
}
SingleHeroPathfinderConfig::SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero)
: PathfinderConfig(std::make_shared<NodeStorage>(out, hero), buildRuleSet())
{
pathfinderHelper.reset(new CPathfinderHelper(gs, hero, options));
}
CPathfinderHelper * SingleHeroPathfinderConfig::getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs)
{
return pathfinderHelper.get();
}
CPathfinder::CPathfinder(
CGameState * _gs,
const CGHeroInstance * _hero,
std::shared_ptr<PathfinderConfig> config)
: CGameInfoCallback(_gs, boost::optional<PlayerColor>())
, hero(_hero)
, patrolTiles({})
, config(config)
, source()
, destination()
{
assert(hero);
assert(hero == getHero(hero->id));
hlp = make_unique<CPathfinderHelper>(_gs, hero, config->options);
initializePatrol();
initializeGraph();
}
@ -316,31 +325,40 @@ void CPathfinder::calculatePaths()
//logGlobal->info("Calculating paths for hero %s (adress %d) of player %d", hero->name, hero , hero->tempOwner);
//initial tile - set cost on 0 and add to the queue
CGPathNode * initialNode = config->nodeStorage->getInitialNode();
std::vector<CGPathNode *> initialNodes = config->nodeStorage->getInitialNodes();
int counter = 0;
if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input
for(auto initialNode : initialNodes)
{
logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you...");
throw std::runtime_error("Wrong checksum");
if(!isInTheMap(initialNode->coord)/* || !gs->map->isInTheMap(dest)*/) //check input
{
logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you...");
throw std::runtime_error("Wrong checksum");
}
source.setNode(gs, initialNode);
auto hlp = config->getOrCreatePathfinderHelper(source, gs);
if(hlp->isHeroPatrolLocked())
break;
pq.push(initialNode);
}
if(isHeroPatrolLocked())
return;
push(initialNode);
while(!pq.empty())
{
counter++;
auto node = topAndPop();
auto excludeOurHero = node->coord == initialNode->coord;
source.setNode(gs, node, excludeOurHero);
source.setNode(gs, node);
source.node->locked = true;
int movement = source.node->moveRemains;
uint8_t turn = source.node->turns;
float cost = source.node->getCost();
auto hlp = config->getOrCreatePathfinderHelper(source, gs);
hlp->updateTurnInfo(turn);
if(!movement)
{
@ -350,24 +368,24 @@ void CPathfinder::calculatePaths()
continue;
}
source.guarded = isSourceGuarded();
if(source.nodeObject)
source.objectRelations = gs->getPlayerRelations(hero->tempOwner, source.nodeObject->tempOwner);
source.isInitialPosition = source.nodeHero == hlp->hero;
source.updateInfo(hlp, gs);
//add accessible neighbouring nodes to the queue
auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp.get());
auto neighbourNodes = config->nodeStorage->calculateNeighbours(source, config.get(), hlp);
for(CGPathNode * neighbour : neighbourNodes)
{
if(neighbour->locked)
continue;
if(!isPatrolMovementAllowed(neighbour->coord))
continue;
if(!hlp->isLayerAvailable(neighbour->layer))
continue;
destination.setNode(gs, neighbour);
hlp = config->getOrCreatePathfinderHelper(destination, gs);
if(!hlp->isPatrolMovementAllowed(neighbour->coord))
continue;
/// Check transition without tile accessability rules
if(source.node->layer != neighbour->layer && !isLayerTransitionPossible())
@ -376,14 +394,12 @@ void CPathfinder::calculatePaths()
destination.turn = turn;
destination.movementLeft = movement;
destination.cost = cost;
destination.guarded = isDestinationGuarded();
destination.updateInfo(hlp, gs);
destination.isGuardianTile = destination.guarded && isDestinationGuardian();
if(destination.nodeObject)
destination.objectRelations = gs->getPlayerRelations(hero->tempOwner, destination.nodeObject->tempOwner);
for(auto rule : config->rules)
{
rule->process(source, destination, config.get(), hlp.get());
rule->process(source, destination, config.get(), hlp);
if(destination.blocked)
break;
@ -395,13 +411,14 @@ void CPathfinder::calculatePaths()
} //neighbours loop
//just add all passable teleport exits
hlp = config->getOrCreatePathfinderHelper(source, gs);
/// For now we disable teleports usage for patrol movement
/// VCAI not aware about patrol and may stuck while attempt to use teleport
if(patrolState == PATROL_RADIUS)
if(hlp->patrolState == CPathfinderHelper::PATROL_RADIUS)
continue;
auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp.get());
auto teleportationNodes = config->nodeStorage->calculateTeleportations(source, config.get(), hlp);
for(CGPathNode * teleportNode : teleportationNodes)
{
if(teleportNode->locked)
@ -429,6 +446,8 @@ void CPathfinder::calculatePaths()
}
}
} //queue loop
logAi->trace("CPathfinder finished with %s iterations", std::to_string(counter));
}
std::vector<int3> CPathfinderHelper::getAllowedTeleportChannelExits(TeleportChannelID channelID) const
@ -498,12 +517,12 @@ std::vector<int3> CPathfinderHelper::getTeleportExits(const PathNodeInfo & sourc
return teleportationExits;
}
bool CPathfinder::isHeroPatrolLocked() const
bool CPathfinderHelper::isHeroPatrolLocked() const
{
return patrolState == PATROL_LOCKED;
}
bool CPathfinder::isPatrolMovementAllowed(const int3 & dst) const
bool CPathfinderHelper::isPatrolMovementAllowed(const int3 & dst) const
{
if(patrolState == PATROL_RADIUS)
{
@ -527,7 +546,7 @@ bool CPathfinder::isLayerTransitionPossible() const
case ELayer::LAND:
if(destLayer == ELayer::AIR)
{
if(!config->options.lightweightFlyingMode || isSourceInitialPosition())
if(!config->options.lightweightFlyingMode || source.isInitialPosition)
return true;
}
else if(destLayer == ELayer::SAIL)
@ -667,7 +686,7 @@ PathfinderBlockingRule::BlockingReason MovementToDestinationRule::getBlockingRea
if(!destination.isNodeObjectVisitable())
return BlockingReason::DESTINATION_BLOCKED;
if(destination.nodeObject->ID != Obj::BOAT && destination.nodeObject->ID != Obj::HERO)
if(destination.nodeObject->ID != Obj::BOAT && !destination.nodeHero)
return BlockingReason::DESTINATION_BLOCKED;
}
else if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::BOAT)
@ -811,9 +830,9 @@ void DestinationActionRule::process(
if(destination.nodeObject->ID == Obj::BOAT)
action = CGPathNode::EMBARK;
else if(destination.nodeObject->ID == Obj::HERO)
else if(destination.nodeHero)
{
if(objRel == PlayerRelations::ENEMIES)
if(destination.heroRelations == PlayerRelations::ENEMIES)
action = CGPathNode::BATTLE;
else
action = CGPathNode::BLOCKING_VISIT;
@ -870,10 +889,10 @@ void DestinationActionRule::process(
CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const
{
CGPathNode::ENodeAction action = CGPathNode::TELEPORT_NORMAL;
if(destination.isNodeObjectVisitable() && destination.nodeObject->ID == Obj::HERO)
if(destination.isNodeObjectVisitable() && destination.nodeHero)
{
auto objRel = getPlayerRelations(destination.nodeObject->tempOwner, hero->tempOwner);
if(objRel == PlayerRelations::ENEMIES)
if(destination.heroRelations == PlayerRelations::ENEMIES)
action = CGPathNode::TELEPORT_BATTLE;
else
action = CGPathNode::TELEPORT_BLOCKING_VISIT;
@ -882,41 +901,15 @@ CGPathNode::ENodeAction CPathfinder::getTeleportDestAction() const
return action;
}
bool CPathfinder::isSourceInitialPosition() const
{
return source.node->coord == config->nodeStorage->getInitialNode()->coord;
}
bool CPathfinder::isSourceGuarded() const
{
/// Hero can move from guarded tile if movement started on that tile
/// It's possible at least in these cases:
/// - Map start with hero on guarded tile
/// - Dimention door used
/// TODO: check what happen when there is several guards
if(gs->guardingCreaturePosition(source.node->coord).valid() && !isSourceInitialPosition())
{
return true;
}
return false;
}
bool CPathfinder::isDestinationGuarded() const
{
/// isDestinationGuarded is exception needed for garrisons.
/// When monster standing behind garrison it's visitable and guarded at the same time.
return gs->guardingCreaturePosition(destination.node->coord).valid();
}
bool CPathfinder::isDestinationGuardian() const
{
return gs->guardingCreaturePosition(source.node->coord) == destination.node->coord;
}
void CPathfinder::initializePatrol()
void CPathfinderHelper::initializePatrol()
{
auto state = PATROL_NONE;
if(hero->patrol.patrolling && !getPlayerState(hero->tempOwner)->human)
{
if(hero->patrol.patrolRadius)
@ -934,7 +927,7 @@ void CPathfinder::initializePatrol()
void CPathfinder::initializeGraph()
{
INodeStorage * nodeStorage = config->nodeStorage.get();
nodeStorage->initialize(config->options, gs, hero);
nodeStorage->initialize(config->options, gs);
}
bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const
@ -1101,10 +1094,11 @@ int TurnInfo::getMaxMovePoints(const EPathfindingLayer layer) const
}
CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options)
: CGameInfoCallback(gs, boost::optional<PlayerColor>()), turn(-1), hero(Hero), options(Options)
: CGameInfoCallback(gs, boost::optional<PlayerColor>()), turn(-1), hero(Hero), options(Options), owner(Hero->tempOwner)
{
turnsInfo.reserve(16);
updateTurnInfo();
initializePatrol();
}
CPathfinderHelper::~CPathfinderHelper()
@ -1349,11 +1343,11 @@ const CGPathNode * CPathsInfo::getNode(const int3 & coord) const
}
PathNodeInfo::PathNodeInfo()
: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false)
: node(nullptr), nodeObject(nullptr), tile(nullptr), coord(-1, -1, -1), guarded(false), isInitialPosition(false)
{
}
void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject)
void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n)
{
node = n;
@ -1363,12 +1357,43 @@ void PathNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObjec
coord = node->coord;
tile = gs->getTile(coord);
nodeObject = tile->topVisitableObj(excludeTopObject);
nodeObject = tile->topVisitableObj();
if(nodeObject && nodeObject->ID == Obj::HERO)
{
nodeHero = dynamic_cast<const CGHeroInstance *>(nodeObject);
nodeObject = tile->topVisitableObj(true);
if(!nodeObject)
nodeObject = nodeHero;
}
else
{
nodeHero = nullptr;
}
}
guarded = false;
}
void PathNodeInfo::updateInfo(CPathfinderHelper * hlp, CGameState * gs)
{
if(gs->guardingCreaturePosition(node->coord).valid() && !isInitialPosition)
{
guarded = true;
}
if(nodeObject)
{
objectRelations = gs->getPlayerRelations(hlp->owner, nodeObject->tempOwner);
}
if(nodeHero)
{
heroRelations = gs->getPlayerRelations(hlp->owner, nodeHero->tempOwner);
}
}
CDestinationNodeInfo::CDestinationNodeInfo()
: PathNodeInfo(),
blocked(false),
@ -1376,9 +1401,9 @@ CDestinationNodeInfo::CDestinationNodeInfo()
{
}
void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject)
void CDestinationNodeInfo::setNode(CGameState * gs, CGPathNode * n)
{
PathNodeInfo::setNode(gs, n, excludeTopObject);
PathNodeInfo::setNode(gs, n);
blocked = false;
action = CGPathNode::ENodeAction::UNKNOWN;
@ -1395,5 +1420,6 @@ bool CDestinationNodeInfo::isBetterWay() const
bool PathNodeInfo::isNodeObjectVisitable() const
{
/// Hero can't visit objects while walking on water or flying
return canSeeObj(nodeObject) && (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL);
return (node->layer == EPathfindingLayer::LAND || node->layer == EPathfindingLayer::SAIL)
&& (canSeeObj(nodeObject) || canSeeObj(nodeHero));
}

View File

@ -196,14 +196,19 @@ struct DLL_LINKAGE PathNodeInfo
{
CGPathNode * node;
const CGObjectInstance * nodeObject;
const CGHeroInstance * nodeHero;
const TerrainTile * tile;
int3 coord;
bool guarded;
PlayerRelations::PlayerRelations objectRelations;
PlayerRelations::PlayerRelations heroRelations;
bool isInitialPosition;
PathNodeInfo();
virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false);
virtual void setNode(CGameState * gs, CGPathNode * n);
void updateInfo(CPathfinderHelper * hlp, CGameState * gs);
bool isNodeObjectVisitable() const;
};
@ -219,7 +224,7 @@ struct DLL_LINKAGE CDestinationNodeInfo : public PathNodeInfo
CDestinationNodeInfo();
virtual void setNode(CGameState * gs, CGPathNode * n, bool excludeTopObject = false) override;
virtual void setNode(CGameState * gs, CGPathNode * n) override;
virtual bool isBetterWay() const;
};
@ -379,7 +384,7 @@ class DLL_LINKAGE INodeStorage
{
public:
using ELayer = EPathfindingLayer;
virtual CGPathNode * getInitialNode() = 0;
virtual std::vector<CGPathNode *> getInitialNodes() = 0;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
@ -393,7 +398,7 @@ public:
virtual void commit(CDestinationNodeInfo & destination, const PathNodeInfo & source) = 0;
virtual void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) = 0;
virtual void initialize(const PathfinderOptions & options, const CGameState * gs) = 0;
};
class DLL_LINKAGE NodeStorage : public INodeStorage
@ -413,9 +418,9 @@ public:
return out.getNode(coord, layer);
}
void initialize(const PathfinderOptions & options, const CGameState * gs, const CGHeroInstance * hero) override;
void initialize(const PathfinderOptions & options, const CGameState * gs) override;
virtual CGPathNode * getInitialNode() override;
virtual std::vector<CGPathNode *> getInitialNodes() override;
virtual std::vector<CGPathNode *> calculateNeighbours(
const PathNodeInfo & source,
@ -440,6 +445,21 @@ public:
PathfinderConfig(
std::shared_ptr<INodeStorage> nodeStorage,
std::vector<std::shared_ptr<IPathfindingRule>> rules);
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) = 0;
};
class DLL_LINKAGE SingleHeroPathfinderConfig : public PathfinderConfig
{
private:
std::unique_ptr<CPathfinderHelper> pathfinderHelper;
public:
SingleHeroPathfinderConfig(CPathsInfo & out, CGameState * gs, const CGHeroInstance * hero);
virtual CPathfinderHelper * getOrCreatePathfinderHelper(const PathNodeInfo & source, CGameState * gs) override;
static std::vector<std::shared_ptr<IPathfindingRule>> buildRuleSet();
};
class CPathfinder : private CGameInfoCallback
@ -450,7 +470,6 @@ public:
CPathfinder(CPathsInfo & _out, CGameState * _gs, const CGHeroInstance * _hero);
CPathfinder(
CGameState * _gs,
const CGHeroInstance * _hero,
std::shared_ptr<PathfinderConfig> config);
void calculatePaths(); //calculates possible paths for hero, uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
@ -458,34 +477,18 @@ public:
private:
typedef EPathfindingLayer ELayer;
const CGHeroInstance * hero;
std::unique_ptr<CPathfinderHelper> hlp;
std::shared_ptr<PathfinderConfig> config;
enum EPatrolState {
PATROL_NONE = 0,
PATROL_LOCKED = 1,
PATROL_RADIUS
} patrolState;
std::unordered_set<int3, ShashInt3> patrolTiles;
boost::heap::fibonacci_heap<CGPathNode *, boost::heap::compare<NodeComparer<CGPathNode>> > pq;
PathNodeInfo source; //current (source) path node -> we took it from the queue
CDestinationNodeInfo destination; //destination node -> it's a neighbour of source that we consider
bool isHeroPatrolLocked() const;
bool isPatrolMovementAllowed(const int3 & dst) const;
bool isLayerTransitionPossible() const;
CGPathNode::ENodeAction getTeleportDestAction() const;
bool isSourceInitialPosition() const;
bool isSourceGuarded() const;
bool isDestinationGuarded() const;
bool isDestinationGuardian() const;
void initializePatrol();
void initializeGraph();
STRONG_INLINE
@ -527,13 +530,25 @@ struct DLL_LINKAGE TurnInfo
class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback
{
public:
enum EPatrolState
{
PATROL_NONE = 0,
PATROL_LOCKED = 1,
PATROL_RADIUS
} patrolState;
std::unordered_set<int3, ShashInt3> patrolTiles;
int turn;
PlayerColor owner;
const CGHeroInstance * hero;
std::vector<TurnInfo *> turnsInfo;
const PathfinderOptions & options;
CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
~CPathfinderHelper();
void initializePatrol();
bool isHeroPatrolLocked() const;
bool isPatrolMovementAllowed(const int3 & dst) const;
void updateTurnInfo(const int turn = 0);
bool isLayerAvailable(const EPathfindingLayer layer) const;
const TurnInfo * getTurnInfo() const;

View File

@ -901,8 +901,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
if (CBonusSystemNode::cachingEnabled && limitOnUs)
{
// Exclusive access for one thread
static boost::mutex m;
boost::mutex::scoped_lock lock(m);
boost::lock_guard<boost::mutex> lock(sync);
// If the bonus system tree changes(state of a single node or the relations to each other) then
// cache all bonus objects. Selector objects doesn't matter.
@ -993,7 +992,8 @@ CBonusSystemNode::CBonusSystemNode()
: bonuses(true),
exportedBonuses(true),
nodeType(UNKNOWN),
cachedLast(0)
cachedLast(0),
sync()
{
}
@ -1001,7 +1001,8 @@ CBonusSystemNode::CBonusSystemNode(ENodeTypes NodeType)
: bonuses(true),
exportedBonuses(true),
nodeType(NodeType),
cachedLast(0)
cachedLast(0),
sync()
{
}
@ -1010,7 +1011,8 @@ CBonusSystemNode::CBonusSystemNode(CBonusSystemNode && other):
exportedBonuses(std::move(other.exportedBonuses)),
nodeType(other.nodeType),
description(other.description),
cachedLast(0)
cachedLast(0),
sync()
{
std::swap(parents, other.parents);
std::swap(children, other.children);
@ -1189,7 +1191,6 @@ void CBonusSystemNode::childDetached(CBonusSystemNode *child)
logBonus->error("Error! %s #cannot be detached from# %s", child->nodeName(), nodeName());
throw std::runtime_error("internal error");
}
}
void CBonusSystemNode::detachFromAll()

View File

@ -761,6 +761,7 @@ private:
// This string needs to be unique, that's why it has to be setted in the following manner:
// [property key]_[value] => only for selector
mutable std::map<std::string, TBonusListPtr > cachedRequests;
mutable boost::mutex sync;
void getBonusesRec(BonusList &out, const CSelector &selector, const CSelector &limit) const;
void getAllBonusesRec(BonusList &out) const;

View File

@ -89,19 +89,24 @@ void CArmedInstance::updateMoraleBonusFromArmy()
factionsInArmy -= mixableFactions - 1;
}
std::string description;
if(factionsInArmy == 1)
{
b->val = +1;
b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
b->description = b->description.substr(0, b->description.size()-3);//trim "+1"
description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
description = description.substr(0, description.size()-3);//trim "+1"
}
else if (!factions.empty()) // no bonus from empty garrison
{
b->val = 2 - (si32)factionsInArmy;
b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
b->description = b->description.substr(0, b->description.size()-2);//trim value
description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d
description = b->description.substr(0, description.size()-2);//trim value
}
boost::algorithm::trim(b->description);
boost::algorithm::trim(description);
b->description = description;
CBonusSystemNode::treeHasChanged();
//-1 modifier for any Undead unit in army

View File

@ -1,12 +1,12 @@
/*
* CommonConstructors.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
*
*/
* CommonConstructors.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 "CommonConstructors.h"
@ -29,7 +29,7 @@ bool CObstacleConstructor::isStaticObject()
return true;
}
CTownInstanceConstructor::CTownInstanceConstructor():
CTownInstanceConstructor::CTownInstanceConstructor() :
faction(nullptr)
{
}
@ -47,7 +47,7 @@ void CTownInstanceConstructor::initTypeData(const JsonNode & input)
void CTownInstanceConstructor::afterLoadFinalization()
{
assert(faction);
for (auto entry : filtersJson.Struct())
for(auto entry : filtersJson.Struct())
{
filters[entry.first] = LogicalExpression<BuildingID>(entry.second, [this](const JsonNode & node)
{
@ -79,7 +79,7 @@ CGObjectInstance * CTownInstanceConstructor::create(const ObjectTemplate & tmpl)
void CTownInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const
{
auto templ = getOverride(object->cb->getTile(object->pos)->terType, object);
if (templ)
if(templ)
object->appearance = templ.get();
}
@ -91,15 +91,17 @@ CHeroInstanceConstructor::CHeroInstanceConstructor()
void CHeroInstanceConstructor::initTypeData(const JsonNode & input)
{
VLC->modh->identifiers.requestIdentifier("heroClass", input["heroClass"],
[&](si32 index) { heroClass = VLC->heroh->classes[index]; });
VLC->modh->identifiers.requestIdentifier(
"heroClass",
input["heroClass"],
[&](si32 index) { heroClass = VLC->heroh->classes[index]; });
filtersJson = input["filters"];
}
void CHeroInstanceConstructor::afterLoadFinalization()
{
for (auto entry : filtersJson.Struct())
for(auto entry : filtersJson.Struct())
{
filters[entry.first] = LogicalExpression<HeroTypeID>(entry.second, [](const JsonNode & node)
{
@ -117,7 +119,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, con
return hero->type->ID == id;
};
if (filters.count(templ.stringID))
if(filters.count(templ.stringID))
{
return filters.at(templ.stringID).test(heroTest);
}
@ -175,9 +177,9 @@ CGObjectInstance * CDwellingInstanceConstructor::create(const ObjectTemplate & t
CGDwelling * obj = createTyped(tmpl);
obj->creatures.resize(availableCreatures.size());
for (auto & entry : availableCreatures)
for(auto & entry : availableCreatures)
{
for (const CCreature * cre : entry)
for(const CCreature * cre : entry)
obj->creatures.back().second.push_back(cre->idNumber);
}
return obj;
@ -190,34 +192,34 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR
dwelling->creatures.clear();
dwelling->creatures.reserve(availableCreatures.size());
for (auto & entry : availableCreatures)
for(auto & entry : availableCreatures)
{
dwelling->creatures.resize(dwelling->creatures.size() + 1);
for (const CCreature * cre : entry)
for(const CCreature * cre : entry)
dwelling->creatures.back().second.push_back(cre->idNumber);
}
bool guarded = false; //TODO: serialize for sanity
if (guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch
if(guards.getType() == JsonNode::JsonType::DATA_BOOL) //simple switch
{
if (guards.Bool())
if(guards.Bool())
{
guarded = true;
}
}
else if (guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux)
else if(guards.getType() == JsonNode::JsonType::DATA_VECTOR) //custom guards (eg. Elemental Conflux)
{
for (auto & stack : JsonRandom::loadCreatures(guards, rng))
for(auto & stack : JsonRandom::loadCreatures(guards, rng))
{
dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count));
}
}
else //default condition - creatures are of level 5 or higher
{
for (auto creatureEntry : availableCreatures)
for(auto creatureEntry : availableCreatures)
{
if (creatureEntry.at(0)->level >= 5)
if(creatureEntry.at(0)->level >= 5)
{
guarded = true;
break;
@ -225,22 +227,22 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR
}
}
if (guarded)
if(guarded)
{
for (auto creatureEntry : availableCreatures)
for(auto creatureEntry : availableCreatures)
{
const CCreature * crea = creatureEntry.at(0);
dwelling->putStack (SlotID(dwelling->stacksCount()), new CStackInstance(crea->idNumber, crea->growth * 3));
dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(crea->idNumber, crea->growth * 3));
}
}
}
bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) const
{
for (auto & entry : availableCreatures)
for(auto & entry : availableCreatures)
{
for (const CCreature * cre : entry)
if (crea == cre)
for(const CCreature * cre : entry)
if(crea == cre)
return true;
}
return false;
@ -249,9 +251,9 @@ bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) cons
std::vector<const CCreature *> CDwellingInstanceConstructor::getProducedCreatures() const
{
std::vector<const CCreature *> creatures; //no idea why it's 2D, to be honest
for (auto & entry : availableCreatures)
for(auto & entry : availableCreatures)
{
for (const CCreature * cre : entry)
for(const CCreature * cre : entry)
creatures.push_back(cre);
}
return creatures;
@ -292,7 +294,7 @@ BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRan
bc.resources = Res::ResourceSet(level["reward"]["resources"]);
bc.creatures = JsonRandom::loadCreatures(level["reward"]["creatures"], rng);
bc.artifacts = JsonRandom::loadArtifacts(level["reward"]["artifacts"], rng);
bc.spells = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells);
bc.spells = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells);
bc.value = static_cast<ui32>(level["value"].Float());
@ -314,18 +316,18 @@ void CBankInstanceConstructor::configureObject(CGObjectInstance * object, CRando
si32 selectedChance = rng.nextInt(totalChance - 1);
int cumulativeChance = 0;
for (auto & node : levels)
for(auto & node : levels)
{
cumulativeChance += static_cast<int>(node["chance"].Float());
if (selectedChance < cumulativeChance)
if(selectedChance < cumulativeChance)
{
bank->setConfig(generateConfig(node, rng));
break;
bank->setConfig(generateConfig(node, rng));
break;
}
}
}
CBankInfo::CBankInfo(const JsonVector & Config):
CBankInfo::CBankInfo(const JsonVector & Config) :
config(Config)
{
assert(!Config.empty());
@ -336,28 +338,28 @@ static void addStackToArmy(IObjectInfo::CArmyStructure & army, const CCreature *
army.totalStrength += crea->fightValue * amount;
bool walker = true;
if (crea->hasBonusOfType(Bonus::SHOOTER))
if(crea->hasBonusOfType(Bonus::SHOOTER))
{
army.shootersStrength += crea->fightValue * amount;
walker = false;
}
if (crea->hasBonusOfType(Bonus::FLYING))
if(crea->hasBonusOfType(Bonus::FLYING))
{
army.flyersStrength += crea->fightValue * amount;
walker = false;
}
if (walker)
if(walker)
army.walkersStrength += crea->fightValue * amount;
}
IObjectInfo::CArmyStructure CBankInfo::minGuards() const
{
std::vector<IObjectInfo::CArmyStructure> armies;
for (auto configEntry : config)
for(auto configEntry : config)
{
auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]);
IObjectInfo::CArmyStructure army;
for (auto & stack : stacks)
for(auto & stack : stacks)
{
assert(!stack.allowedCreatures.empty());
auto weakest = boost::range::min_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b)
@ -374,11 +376,11 @@ IObjectInfo::CArmyStructure CBankInfo::minGuards() const
IObjectInfo::CArmyStructure CBankInfo::maxGuards() const
{
std::vector<IObjectInfo::CArmyStructure> armies;
for (auto configEntry : config)
for(auto configEntry : config)
{
auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]);
IObjectInfo::CArmyStructure army;
for (auto & stack : stacks)
for(auto & stack : stacks)
{
assert(!stack.allowedCreatures.empty());
auto strongest = boost::range::max_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b)
@ -396,14 +398,14 @@ TPossibleGuards CBankInfo::getPossibleGuards() const
{
TPossibleGuards out;
for (const JsonNode & configEntry : config)
for(const JsonNode & configEntry : config)
{
const JsonNode & guardsInfo = configEntry["guards"];
auto stacks = JsonRandom::evaluateCreatures(guardsInfo);
IObjectInfo::CArmyStructure army;
for (auto stack : stacks)
for(auto stack : stacks)
{
army.totalStrength += stack.allowedCreatures.front()->AIValue * (stack.minAmount + stack.maxAmount) / 2;
//TODO: add fields for flyers, walkers etc...
@ -415,34 +417,78 @@ TPossibleGuards CBankInfo::getPossibleGuards() const
return out;
}
std::vector<PossibleReward<TResources>> CBankInfo::getPossibleResourcesReward() const
{
std::vector<PossibleReward<TResources>> result;
for(const JsonNode & configEntry : config)
{
const JsonNode & resourcesInfo = configEntry["reward"]["resources"];
if(!resourcesInfo.isNull())
{
result.push_back(
PossibleReward<TResources>(
configEntry["chance"].Integer(),
TResources(resourcesInfo)
));
}
}
return result;
}
std::vector<PossibleReward<CStackBasicDescriptor>> CBankInfo::getPossibleCreaturesReward() const
{
std::vector<PossibleReward<CStackBasicDescriptor>> aproximateReward;
for(const JsonNode & configEntry : config)
{
const JsonNode & guardsInfo = configEntry["reward"]["creatures"];
auto stacks = JsonRandom::evaluateCreatures(guardsInfo);
for(auto stack : stacks)
{
auto creature = stack.allowedCreatures.front();
aproximateReward.push_back(
PossibleReward<CStackBasicDescriptor>(
configEntry["chance"].Integer(),
CStackBasicDescriptor(creature, (stack.minAmount + stack.maxAmount) / 2)));
}
}
return aproximateReward;
}
bool CBankInfo::givesResources() const
{
for (const JsonNode & node : config)
if (!node["reward"]["resources"].isNull())
for(const JsonNode & node : config)
if(!node["reward"]["resources"].isNull())
return true;
return false;
}
bool CBankInfo::givesArtifacts() const
{
for (const JsonNode & node : config)
if (!node["reward"]["artifacts"].isNull())
for(const JsonNode & node : config)
if(!node["reward"]["artifacts"].isNull())
return true;
return false;
}
bool CBankInfo::givesCreatures() const
{
for (const JsonNode & node : config)
if (!node["reward"]["creatures"].isNull())
for(const JsonNode & node : config)
if(!node["reward"]["creatures"].isNull())
return true;
return false;
}
bool CBankInfo::givesSpells() const
{
for (const JsonNode & node : config)
if (!node["reward"]["spells"].isNull())
for(const JsonNode & node : config)
if(!node["reward"]["spells"].isNull())
return true;
return false;
}

View File

@ -1,12 +1,12 @@
/*
* CommonConstructors.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
*
*/
* CommonConstructors.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 "CObjectClassesHandler.h"
@ -34,7 +34,7 @@ protected:
return obj;
}
public:
CDefaultObjectTypeHandler(){}
CDefaultObjectTypeHandler() {}
CGObjectInstance * create(const ObjectTemplate & tmpl) const override
{
@ -164,6 +164,15 @@ struct BankConfig
typedef std::vector<std::pair<ui8, IObjectInfo::CArmyStructure>> TPossibleGuards;
template <typename T>
struct DLL_LINKAGE PossibleReward
{
int chance;
T data;
PossibleReward(int chance, const T & data) : chance(chance), data(data) {}
};
class DLL_LINKAGE CBankInfo : public IObjectInfo
{
const JsonVector & config;
@ -171,6 +180,8 @@ public:
CBankInfo(const JsonVector & Config);
TPossibleGuards getPossibleGuards() const;
std::vector<PossibleReward<TResources>> getPossibleResourcesReward() const;
std::vector<PossibleReward<CStackBasicDescriptor>> getPossibleCreaturesReward() const;
// These functions should try to evaluate minimal possible/max possible guards to give provide information on possible thread to AI
CArmyStructure minGuards() const override;