mirror of
https://github.com/vcmi/vcmi.git
synced 2025-06-23 00:28:08 +02:00
Merge remote-tracking branch 'origin/develop' into custom_objects_per_zone
# Conflicts: # lib/rmg/CRmgTemplate.cpp
This commit is contained in:
5
.github/workflows/github.yml
vendored
5
.github/workflows/github.yml
vendored
@ -183,6 +183,11 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
# a hack to build ID for x64 build in order for Google Play to allow upload of both 32 and 64 bit builds
|
||||
- name: Bump Android x64 build ID
|
||||
if: ${{ matrix.platform == 'android-64' }}
|
||||
run: perl -i -pe 's/versionCode (\d+)/$x=$1+1; "versionCode $x"/e' android/vcmi-app/build.gradle
|
||||
|
||||
- name: Build Number
|
||||
run: |
|
||||
source '${{github.workspace}}/CI/get_package_name.sh'
|
||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,7 +1,7 @@
|
||||
[submodule "test/googletest"]
|
||||
path = test/googletest
|
||||
url = https://github.com/google/googletest
|
||||
branch = v1.13.x
|
||||
branch = v1.15.x
|
||||
[submodule "AI/FuzzyLite"]
|
||||
path = AI/FuzzyLite
|
||||
url = https://github.com/fuzzylite/fuzzylite.git
|
||||
|
@ -12,6 +12,10 @@
|
||||
#include "../../lib/CStack.h" // TODO: remove
|
||||
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||
// CUnitState should be private and CStack should be removed completely
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/spells/ObstacleCasterProxy.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
|
||||
uint64_t averageDmg(const DamageRange & range)
|
||||
{
|
||||
@ -25,9 +29,57 @@ void DamageCache::cacheDamage(const battle::Unit * attacker, const battle::Unit
|
||||
damageCache[attacker->unitId()][defender->unitId()] = static_cast<float>(damage) / attacker->getCount();
|
||||
}
|
||||
|
||||
|
||||
void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int side)
|
||||
void DamageCache::buildObstacleDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side)
|
||||
{
|
||||
for(const auto & obst : hb->battleGetAllObstacles(side))
|
||||
{
|
||||
auto spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(obst.get());
|
||||
|
||||
if(!spellObstacle || !obst->triggersEffects())
|
||||
continue;
|
||||
|
||||
auto triggerAbility = VLC->spells()->getById(obst->getTrigger());
|
||||
auto triggerIsNegative = triggerAbility->isNegative() || triggerAbility->isDamage();
|
||||
|
||||
if(!triggerIsNegative)
|
||||
continue;
|
||||
|
||||
const auto * hero = hb->battleGetFightingHero(spellObstacle->casterSide);
|
||||
auto caster = spells::ObstacleCasterProxy(hb->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle);
|
||||
|
||||
auto affectedHexes = obst->getAffectedTiles();
|
||||
auto stacks = hb->battleGetUnitsIf([](const battle::Unit * u) -> bool {
|
||||
return u->alive() && !u->isTurret() && u->getPosition().isValid();
|
||||
});
|
||||
|
||||
for(auto stack : stacks)
|
||||
{
|
||||
std::shared_ptr<HypotheticBattle> inner = std::make_shared<HypotheticBattle>(hb->env, hb);
|
||||
auto cast = spells::BattleCast(hb.get(), &caster, spells::Mode::PASSIVE, obst->getTrigger().toSpell());
|
||||
auto updated = inner->getForUpdate(stack->unitId());
|
||||
|
||||
spells::Target target;
|
||||
target.push_back(spells::Destination(updated.get()));
|
||||
|
||||
cast.castEval(inner->getServerCallback(), target);
|
||||
|
||||
auto damageDealt = stack->getAvailableHealth() - updated->getAvailableHealth();
|
||||
|
||||
for(auto hex : affectedHexes)
|
||||
{
|
||||
obstacleDamage[hex][stack->unitId()] = damageDealt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DamageCache::buildDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side)
|
||||
{
|
||||
if(parent == nullptr)
|
||||
{
|
||||
buildObstacleDamageCache(hb, side);
|
||||
}
|
||||
|
||||
auto stacks = hb->battleGetUnitsIf([=](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->isValidTarget();
|
||||
@ -70,6 +122,23 @@ int64_t DamageCache::getDamage(const battle::Unit * attacker, const battle::Unit
|
||||
return damageCache[attacker->unitId()][defender->unitId()] * attacker->getCount();
|
||||
}
|
||||
|
||||
int64_t DamageCache::getObstacleDamage(BattleHex hex, const battle::Unit * defender)
|
||||
{
|
||||
if(parent)
|
||||
return parent->getObstacleDamage(hex, defender);
|
||||
|
||||
auto damages = obstacleDamage.find(hex);
|
||||
|
||||
if(damages == obstacleDamage.end())
|
||||
return 0;
|
||||
|
||||
auto damage = damages->second.find(defender->unitId());
|
||||
|
||||
return damage == damages->second.end()
|
||||
? 0
|
||||
: damage->second;
|
||||
}
|
||||
|
||||
int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb)
|
||||
{
|
||||
if(parent)
|
||||
@ -93,6 +162,8 @@ int64_t DamageCache::getOriginalDamage(const battle::Unit * attacker, const batt
|
||||
AttackPossibility::AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack)
|
||||
: from(from), dest(dest), attack(attack)
|
||||
{
|
||||
this->attack.attackerPos = from;
|
||||
this->attack.defenderPos = dest;
|
||||
}
|
||||
|
||||
float AttackPossibility::damageDiff() const
|
||||
@ -199,6 +270,8 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
if(attackInfo.shooting)
|
||||
return 0;
|
||||
|
||||
std::set<uint32_t> checkedUnits;
|
||||
|
||||
auto attacker = attackInfo.attacker;
|
||||
auto hexes = attacker->getSurroundingHexes(hex);
|
||||
for(BattleHex tile : hexes)
|
||||
@ -206,9 +279,13 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
auto st = state->battleGetUnitByPos(tile, true);
|
||||
if(!st || !state->battleMatchOwner(st, attacker))
|
||||
continue;
|
||||
if(vstd::contains(checkedUnits, st->unitId()))
|
||||
continue;
|
||||
if(!state->battleCanShoot(st))
|
||||
continue;
|
||||
|
||||
checkedUnits.insert(st->unitId());
|
||||
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
|
||||
rangeAttackInfo.defenderPos = hex;
|
||||
@ -218,9 +295,10 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(
|
||||
|
||||
auto rangeDmg = state->battleEstimateDamage(rangeAttackInfo);
|
||||
auto meleeDmg = state->battleEstimateDamage(meleeAttackInfo);
|
||||
auto cachedDmg = damageCache.getOriginalDamage(st, attacker, state);
|
||||
|
||||
int64_t gain = averageDmg(rangeDmg.damage) - averageDmg(meleeDmg.damage) + 1;
|
||||
res += gain;
|
||||
res += gain * cachedDmg / std::max<uint64_t>(1, averageDmg(rangeDmg.damage));
|
||||
}
|
||||
|
||||
return res;
|
||||
@ -243,7 +321,7 @@ AttackPossibility AttackPossibility::evaluate(
|
||||
|
||||
std::vector<BattleHex> defenderHex;
|
||||
if(attackInfo.shooting)
|
||||
defenderHex = defender->getHexes();
|
||||
defenderHex.push_back(defender->getPosition());
|
||||
else
|
||||
defenderHex = CStack::meleeAttackHexes(attacker, defender, hex);
|
||||
|
||||
@ -261,63 +339,114 @@ AttackPossibility AttackPossibility::evaluate(
|
||||
if (!attackInfo.shooting)
|
||||
ap.attackerState->setPosition(hex);
|
||||
|
||||
std::vector<const battle::Unit*> units;
|
||||
std::vector<const battle::Unit *> defenderUnits;
|
||||
std::vector<const battle::Unit *> retaliatedUnits = {attacker};
|
||||
std::vector<const battle::Unit *> affectedUnits;
|
||||
|
||||
if (attackInfo.shooting)
|
||||
units = state->getAttackedBattleUnits(attacker, defHex, true, BattleHex::INVALID);
|
||||
defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, true, hex, defender->getPosition());
|
||||
else
|
||||
units = state->getAttackedBattleUnits(attacker, defHex, false, hex);
|
||||
{
|
||||
defenderUnits = state->getAttackedBattleUnits(attacker, defender, defHex, false, hex, defender->getPosition());
|
||||
retaliatedUnits = state->getAttackedBattleUnits(defender, attacker, hex, false, defender->getPosition(), hex);
|
||||
|
||||
// attacker can not melle-attack itself but still can hit that place where it was before moving
|
||||
vstd::erase_if(defenderUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); });
|
||||
|
||||
if(!vstd::contains_if(retaliatedUnits, [attacker](const battle::Unit * u) -> bool { return u->unitId() == attacker->unitId(); }))
|
||||
{
|
||||
retaliatedUnits.push_back(attacker);
|
||||
}
|
||||
|
||||
auto obstacleDamage = damageCache.getObstacleDamage(hex, attacker);
|
||||
|
||||
if(obstacleDamage > 0)
|
||||
{
|
||||
ap.attackerDamageReduce += calculateDamageReduce(nullptr, attacker, obstacleDamage, damageCache, state);
|
||||
|
||||
ap.attackerState->damage(obstacleDamage);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the defender is also affected
|
||||
bool addDefender = true;
|
||||
for(auto unit : units)
|
||||
if(!vstd::contains_if(defenderUnits, [defender](const battle::Unit * u) -> bool { return u->unitId() == defender->unitId(); }))
|
||||
{
|
||||
if (unit->unitId() == defender->unitId())
|
||||
{
|
||||
addDefender = false;
|
||||
break;
|
||||
}
|
||||
defenderUnits.push_back(defender);
|
||||
}
|
||||
|
||||
if(addDefender)
|
||||
units.push_back(defender);
|
||||
affectedUnits = defenderUnits;
|
||||
vstd::concatenate(affectedUnits, retaliatedUnits);
|
||||
|
||||
for(auto u : units)
|
||||
logAi->trace("Attacked battle units count %d, %d->%d", affectedUnits.size(), hex.hex, defHex.hex);
|
||||
|
||||
std::map<uint32_t, std::shared_ptr<battle::CUnitState>> defenderStates;
|
||||
|
||||
for(auto u : affectedUnits)
|
||||
{
|
||||
if(!ap.attackerState->alive())
|
||||
break;
|
||||
if(u->unitId() == attacker->unitId())
|
||||
continue;
|
||||
|
||||
auto defenderState = u->acquireState();
|
||||
|
||||
ap.affectedUnits.push_back(defenderState);
|
||||
defenderStates[u->unitId()] = defenderState;
|
||||
}
|
||||
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
{
|
||||
if(!ap.attackerState->alive() || !defenderStates[defender->unitId()]->alive())
|
||||
break;
|
||||
|
||||
for(auto u : defenderUnits)
|
||||
{
|
||||
auto defenderState = defenderStates.at(u->unitId());
|
||||
|
||||
int64_t damageDealt;
|
||||
int64_t damageReceived;
|
||||
float defenderDamageReduce;
|
||||
float attackerDamageReduce;
|
||||
|
||||
DamageEstimation retaliation;
|
||||
auto attackDmg = state->battleEstimateDamage(ap.attack, &retaliation);
|
||||
|
||||
vstd::amin(attackDmg.damage.min, defenderState->getAvailableHealth());
|
||||
vstd::amin(attackDmg.damage.max, defenderState->getAvailableHealth());
|
||||
|
||||
vstd::amin(retaliation.damage.min, ap.attackerState->getAvailableHealth());
|
||||
vstd::amin(retaliation.damage.max, ap.attackerState->getAvailableHealth());
|
||||
|
||||
damageDealt = averageDmg(attackDmg.damage);
|
||||
defenderDamageReduce = calculateDamageReduce(attacker, defender, damageDealt, damageCache, state);
|
||||
vstd::amin(damageDealt, defenderState->getAvailableHealth());
|
||||
|
||||
defenderDamageReduce = calculateDamageReduce(attacker, u, damageDealt, damageCache, state);
|
||||
ap.attackerState->afterAttack(attackInfo.shooting, false);
|
||||
|
||||
//FIXME: use ranged retaliation
|
||||
damageReceived = 0;
|
||||
attackerDamageReduce = 0;
|
||||
|
||||
if (!attackInfo.shooting && defenderState->ableToRetaliate() && !counterAttacksBlocked)
|
||||
if (!attackInfo.shooting && u->unitId() == defender->unitId() && defenderState->ableToRetaliate() && !counterAttacksBlocked)
|
||||
{
|
||||
damageReceived = averageDmg(retaliation.damage);
|
||||
attackerDamageReduce = calculateDamageReduce(defender, attacker, damageReceived, damageCache, state);
|
||||
for(auto retaliated : retaliatedUnits)
|
||||
{
|
||||
if(retaliated->unitId() == attacker->unitId())
|
||||
{
|
||||
int64_t damageReceived = averageDmg(retaliation.damage);
|
||||
|
||||
vstd::amin(damageReceived, ap.attackerState->getAvailableHealth());
|
||||
|
||||
attackerDamageReduce = calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state);
|
||||
ap.attackerState->damage(damageReceived);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto retaliationCollateral = state->battleEstimateDamage(defender, retaliated, 0);
|
||||
int64_t damageReceived = averageDmg(retaliationCollateral.damage);
|
||||
|
||||
vstd::amin(damageReceived, retaliated->getAvailableHealth());
|
||||
|
||||
if(defender->unitSide() == retaliated->unitSide())
|
||||
defenderDamageReduce += calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state);
|
||||
else
|
||||
ap.collateralDamageReduce += calculateDamageReduce(defender, retaliated, damageReceived, damageCache, state);
|
||||
|
||||
defenderStates.at(retaliated->unitId())->damage(damageReceived);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
defenderState->afterAttack(attackInfo.shooting, true);
|
||||
}
|
||||
|
||||
@ -331,20 +460,29 @@ AttackPossibility AttackPossibility::evaluate(
|
||||
if(attackerSide == u->unitSide())
|
||||
ap.collateralDamageReduce += defenderDamageReduce;
|
||||
|
||||
if(u->unitId() == defender->unitId() ||
|
||||
(!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
|
||||
if(u->unitId() == defender->unitId()
|
||||
|| (!attackInfo.shooting && CStack::isMeleeAttackPossible(u, attacker, hex)))
|
||||
{
|
||||
//FIXME: handle RANGED_RETALIATION ?
|
||||
ap.attackerDamageReduce += attackerDamageReduce;
|
||||
}
|
||||
|
||||
ap.attackerState->damage(damageReceived);
|
||||
defenderState->damage(damageDealt);
|
||||
|
||||
if (!ap.attackerState->alive() || !defenderState->alive())
|
||||
break;
|
||||
if(u->unitId() == defender->unitId())
|
||||
{
|
||||
ap.defenderDead = !defenderState->alive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("BattleAI AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
|
||||
attackInfo.attacker->unitType()->getJsonKey(),
|
||||
attackInfo.defender->unitType()->getJsonKey(),
|
||||
(int)ap.dest, (int)ap.from, (int)ap.affectedUnits.size(),
|
||||
ap.defenderDamageReduce, ap.attackerDamageReduce, ap.collateralDamageReduce, ap.shootersBlockedDmg);
|
||||
#endif
|
||||
|
||||
if(!bestAp.dest.isValid() || ap.attackValue() > bestAp.attackValue())
|
||||
bestAp = ap;
|
||||
|
@ -18,16 +18,20 @@ class DamageCache
|
||||
{
|
||||
private:
|
||||
std::unordered_map<uint32_t, std::unordered_map<uint32_t, float>> damageCache;
|
||||
std::map<BattleHex, std::unordered_map<uint32_t, int64_t>> obstacleDamage;
|
||||
DamageCache * parent;
|
||||
|
||||
void buildObstacleDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side);
|
||||
|
||||
public:
|
||||
DamageCache() : parent(nullptr) {}
|
||||
DamageCache(DamageCache * parent) : parent(parent) {}
|
||||
|
||||
void cacheDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||
int64_t getDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||
int64_t getObstacleDamage(BattleHex hex, const battle::Unit * defender);
|
||||
int64_t getOriginalDamage(const battle::Unit * attacker, const battle::Unit * defender, std::shared_ptr<CBattleInfoCallback> hb);
|
||||
void buildDamageCache(std::shared_ptr<HypotheticBattle> hb, int side);
|
||||
void buildDamageCache(std::shared_ptr<HypotheticBattle> hb, BattleSide side);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -49,6 +53,7 @@ public:
|
||||
float attackerDamageReduce = 0; //usually by counter-attack
|
||||
float collateralDamageReduce = 0; // friendly fire (usually by two-hex attacks)
|
||||
int64_t shootersBlockedDmg = 0;
|
||||
bool defenderDead = false;
|
||||
|
||||
AttackPossibility(BattleHex from, BattleHex dest, const BattleAttackInfo & attack_);
|
||||
|
||||
|
@ -23,15 +23,17 @@
|
||||
#include "../../lib/battle/BattleAction.h"
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/CStack.h" // TODO: remove
|
||||
// Eventually only IBattleInfoCallback and battle::Unit should be used,
|
||||
// CUnitState should be private and CStack should be removed completely
|
||||
#include "../../lib/logging/VisualLogger.h"
|
||||
|
||||
#define LOGL(text) print(text)
|
||||
#define LOGFL(text, formattingEl) print(boost::str(boost::format(text) % formattingEl))
|
||||
|
||||
CBattleAI::CBattleAI()
|
||||
: side(-1),
|
||||
: side(BattleSide::NONE),
|
||||
wasWaitingForRealize(false),
|
||||
wasUnlockingGs(false)
|
||||
{
|
||||
@ -47,6 +49,17 @@ CBattleAI::~CBattleAI()
|
||||
}
|
||||
}
|
||||
|
||||
void logHexNumbers()
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logVisual->updateWithLock("hexes", [](IVisualLogBuilder & b)
|
||||
{
|
||||
for(BattleHex hex = BattleHex(0); hex < GameConstants::BFIELD_SIZE; hex = BattleHex(hex + 1))
|
||||
b.addText(hex, std::to_string(hex.hex));
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB)
|
||||
{
|
||||
env = ENV;
|
||||
@ -57,6 +70,8 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
|
||||
CB->waitTillRealize = false;
|
||||
CB->unlockGsWhenWaiting = false;
|
||||
movesSkippedByDefense = 0;
|
||||
|
||||
logHexNumbers();
|
||||
}
|
||||
|
||||
void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences)
|
||||
@ -86,7 +101,7 @@ void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
|
||||
cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
|
||||
}
|
||||
|
||||
static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
|
||||
static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, BattleSide side)
|
||||
{
|
||||
auto stacks = cb->battleGetAllStacks();
|
||||
auto our = 0;
|
||||
@ -108,6 +123,11 @@ static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
|
||||
return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
|
||||
}
|
||||
|
||||
int getSimulationTurnsCount(const StartInfo * startInfo)
|
||||
{
|
||||
return startInfo->difficulty < 4 ? 2 : 10;
|
||||
}
|
||||
|
||||
void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
{
|
||||
LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
|
||||
@ -140,7 +160,10 @@ void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
|
||||
logAi->trace("Build evaluator and targets");
|
||||
#endif
|
||||
|
||||
BattleEvaluator evaluator(env, cb, stack, playerID, battleID, side, getStrengthRatio(cb->getBattle(battleID), side));
|
||||
BattleEvaluator evaluator(
|
||||
env, cb, stack, playerID, battleID, side,
|
||||
getStrengthRatio(cb->getBattle(battleID), side),
|
||||
getSimulationTurnsCount(env->game()->getStartInfo()));
|
||||
|
||||
result = evaluator.selectStackAction(stack);
|
||||
|
||||
@ -206,7 +229,7 @@ BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * st
|
||||
{
|
||||
auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart);
|
||||
|
||||
if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
|
||||
if(wallState != EWallState::NONE && wallState != EWallState::DESTROYED)
|
||||
{
|
||||
targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart);
|
||||
break;
|
||||
@ -229,7 +252,7 @@ BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * st
|
||||
return attack;
|
||||
}
|
||||
|
||||
void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
||||
void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide Side, bool replayAllowed)
|
||||
{
|
||||
LOG_TRACE(logAi);
|
||||
side = Side;
|
||||
|
@ -27,7 +27,7 @@ struct CurrentOffensivePotential
|
||||
std::map<const CStack *, PotentialTargets> ourAttacks;
|
||||
std::map<const CStack *, PotentialTargets> enemyAttacks;
|
||||
|
||||
CurrentOffensivePotential(ui8 side)
|
||||
CurrentOffensivePotential(BattleSide side)
|
||||
{
|
||||
for(auto stack : cbc->battleGetStacks())
|
||||
{
|
||||
@ -54,7 +54,7 @@ struct CurrentOffensivePotential
|
||||
|
||||
class CBattleAI : public CBattleGameInterface
|
||||
{
|
||||
int side;
|
||||
BattleSide side;
|
||||
std::shared_ptr<CBattleCallback> cb;
|
||||
std::shared_ptr<Environment> env;
|
||||
|
||||
@ -80,7 +80,7 @@ public:
|
||||
BattleAction useCatapult(const BattleID & battleID, const CStack *stack);
|
||||
BattleAction useHealingTent(const BattleID & battleID, const CStack *stack);
|
||||
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side, bool replayAllowed) 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;
|
||||
//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
|
||||
//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
|
||||
//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
|
||||
@ -93,7 +93,7 @@ public:
|
||||
//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;
|
||||
//void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
//void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
//void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||
AutocombatPreferences autobattlePreferences = AutocombatPreferences();
|
||||
};
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "../../lib/CStopWatch.h"
|
||||
#include "../../lib/CThreadHelper.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/entities/building/TownFortifications.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
#include "../../lib/spells/ISpellMechanics.h"
|
||||
#include "../../lib/battle/BattleStateInfoForRetreat.h"
|
||||
@ -49,6 +50,43 @@ SpellTypes spellType(const CSpell * spell)
|
||||
return SpellTypes::OTHER;
|
||||
}
|
||||
|
||||
BattleEvaluator::BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
std::shared_ptr<CBattleCallback> cb,
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
|
||||
cachedAttack(), playerID(playerID), side(side), env(env),
|
||||
cb(cb), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
|
||||
{
|
||||
hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
damageCache.buildDamageCache(hb, side);
|
||||
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
}
|
||||
|
||||
BattleEvaluator::BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
std::shared_ptr<CBattleCallback> cb,
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
DamageCache & damageCache,
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio, simulationTurnsCount),
|
||||
cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb),
|
||||
damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID), simulationTurnsCount(simulationTurnsCount)
|
||||
{
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
}
|
||||
|
||||
std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||
{
|
||||
std::vector<BattleHex> result;
|
||||
@ -139,8 +177,10 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
auto evaluationResult = scoreEvaluator.findBestTarget(stack, *targets, damageCache, hb);
|
||||
auto & bestAttack = evaluationResult.bestAttack;
|
||||
|
||||
cachedAttack = bestAttack;
|
||||
cachedScore = evaluationResult.score;
|
||||
cachedAttack.ap = bestAttack;
|
||||
cachedAttack.score = evaluationResult.score;
|
||||
cachedAttack.turn = 0;
|
||||
cachedAttack.waited = evaluationResult.wait;
|
||||
|
||||
//TODO: consider more complex spellcast evaluation, f.e. because "re-retaliation" during enemy move in same turn for melee attack etc.
|
||||
if(bestSpellcast.has_value() && bestSpellcast->value > bestAttack.damageDiff())
|
||||
@ -167,7 +207,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
score
|
||||
);
|
||||
|
||||
if (moveTarget.scorePerTurn <= score)
|
||||
if (moveTarget.score <= score)
|
||||
{
|
||||
if(evaluationResult.wait)
|
||||
{
|
||||
@ -186,37 +226,64 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
{
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
else
|
||||
|
||||
auto enemyMellee = hb->getUnitsIf([this](const battle::Unit * u) -> bool
|
||||
{
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defender->getPosition(), bestAttack.from);
|
||||
return u->unitSide() == BattleSide::ATTACKER && !hb->battleCanShoot(u);
|
||||
});
|
||||
|
||||
bool isTargetOutsideFort = bestAttack.dest.getY() < GameConstants::BFIELD_WIDTH - 4;
|
||||
bool siegeDefense = stack->unitSide() == BattleSide::DEFENDER
|
||||
&& !bestAttack.attack.shooting
|
||||
&& hb->battleGetFortifications().hasMoat
|
||||
&& !enemyMellee.empty()
|
||||
&& isTargetOutsideFort;
|
||||
|
||||
if(siegeDefense)
|
||||
{
|
||||
logAi->trace("Evaluating exchange at %d self-defense", stack->getPosition().hex);
|
||||
|
||||
BattleAttackInfo bai(stack, stack, 0, false);
|
||||
AttackPossibility apDefend(stack->getPosition(), stack->getPosition(), bai);
|
||||
|
||||
float defenseValue = scoreEvaluator.evaluateExchange(apDefend, 0, *targets, damageCache, hb);
|
||||
|
||||
if((defenseValue > score && score <= 0) || (defenseValue > 2 * score && score > 0))
|
||||
{
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, bestAttack.attack.defenderPos, bestAttack.from);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ThreatMap threatsToUs(stack); // These lines may be useful but they are't used in the code.
|
||||
if(moveTarget.scorePerTurn > score)
|
||||
if(moveTarget.score > score)
|
||||
{
|
||||
score = moveTarget.score;
|
||||
cachedAttack = moveTarget.cachedAttack;
|
||||
cachedScore = score;
|
||||
cachedAttack.ap = moveTarget.cachedAttack;
|
||||
cachedAttack.score = score;
|
||||
cachedAttack.turn = moveTarget.turnsToRich;
|
||||
|
||||
if(stack->waited())
|
||||
{
|
||||
logAi->debug(
|
||||
"Moving %s towards hex %s[%d], score: %2f/%2f",
|
||||
"Moving %s towards hex %s[%d], score: %2f",
|
||||
stack->getDescription(),
|
||||
moveTarget.cachedAttack->attack.defender->getDescription(),
|
||||
moveTarget.cachedAttack->attack.defender->getPosition().hex,
|
||||
moveTarget.score,
|
||||
moveTarget.scorePerTurn);
|
||||
moveTarget.score);
|
||||
|
||||
return goTowardsNearest(stack, moveTarget.positions);
|
||||
return goTowardsNearest(stack, moveTarget.positions, *targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedAttack.waited = true;
|
||||
|
||||
return BattleAction::makeWait(stack);
|
||||
}
|
||||
}
|
||||
@ -224,7 +291,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
if(score <= EvaluationResult::INEFFECTIVE_SCORE
|
||||
&& !stack->hasBonusOfType(BonusType::FLYING)
|
||||
&& stack->unitSide() == BattleSide::ATTACKER
|
||||
&& cb->getBattle(battleID)->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
|
||||
&& cb->getBattle(battleID)->battleGetFortifications().hasMoat)
|
||||
{
|
||||
auto brokenWallMoat = getBrokenWallMoatHexes();
|
||||
|
||||
@ -235,7 +302,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
if(stack->doubleWide() && vstd::contains(brokenWallMoat, stack->getPosition()))
|
||||
return BattleAction::makeMove(stack, stack->getPosition().cloneInDirection(BattleHex::RIGHT));
|
||||
else
|
||||
return goTowardsNearest(stack, brokenWallMoat);
|
||||
return goTowardsNearest(stack, brokenWallMoat, *targets);
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,7 +316,32 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
}
|
||||
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes)
|
||||
BattleAction BattleEvaluator::moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets)
|
||||
{
|
||||
auto additionalScore = 0;
|
||||
std::optional<AttackPossibility> attackOnTheWay;
|
||||
|
||||
for(auto & target : targets.possibleAttacks)
|
||||
{
|
||||
if(!target.attack.shooting && target.from == hex && target.attackValue() > additionalScore)
|
||||
{
|
||||
additionalScore = target.attackValue();
|
||||
attackOnTheWay = target;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackOnTheWay)
|
||||
{
|
||||
activeActionMade = true;
|
||||
return BattleAction::makeMeleeAttack(stack, attackOnTheWay->attack.defender->getPosition(), attackOnTheWay->from);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
}
|
||||
|
||||
BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets)
|
||||
{
|
||||
auto reachability = cb->getBattle(battleID)->getReachability(stack);
|
||||
auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
|
||||
@ -261,49 +353,38 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
|
||||
std::vector<BattleHex> targetHexes = hexes;
|
||||
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
|
||||
|
||||
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||
{
|
||||
return reachability.distances[h1] < reachability.distances[h2];
|
||||
});
|
||||
|
||||
BattleHex bestNeighbor = targetHexes.front();
|
||||
|
||||
if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
|
||||
{
|
||||
logAi->trace("No richable hexes.");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
// this turn
|
||||
for(auto hex : targetHexes)
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
return moveOrAttack(stack, hex, targets);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
logAi->warn("Warning: already standing on neighbouring tile!");
|
||||
logAi->warn("Warning: already standing on neighbouring hex!");
|
||||
//We shouldn't even be here...
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
if(reachability.distances[targetHexes.front()] <= GameConstants::BFIELD_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<BattleHex> copy = targetHexes;
|
||||
|
||||
for(auto hex : copy)
|
||||
vstd::concatenate(targetHexes, hex.allNeighbouringTiles());
|
||||
|
||||
vstd::erase_if(targetHexes, [](const BattleHex & hex) {return !hex.isValid();});
|
||||
vstd::removeDuplicates(targetHexes);
|
||||
}
|
||||
|
||||
BattleHex bestNeighbor = targetHexes.front();
|
||||
|
||||
if(reachability.distances[bestNeighbor] > GameConstants::BFIELD_SIZE)
|
||||
{
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
// not this turn
|
||||
scoreEvaluator.updateReachabilityMap(hb);
|
||||
|
||||
if(stack->hasBonusOfType(BonusType::FLYING))
|
||||
@ -343,7 +424,7 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
return scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, hex) ? BLOCKED_STACK_PENALTY + distance : distance;
|
||||
});
|
||||
|
||||
return BattleAction::makeMove(stack, *nearestAvailableHex);
|
||||
return moveOrAttack(stack, *nearestAvailableHex, targets);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -357,11 +438,16 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
|
||||
if(vstd::contains(avHexes, currentDest)
|
||||
&& !scoreEvaluator.checkPositionBlocksOurStacks(*hb, stack, currentDest))
|
||||
return BattleAction::makeMove(stack, currentDest);
|
||||
{
|
||||
return moveOrAttack(stack, currentDest, targets);
|
||||
}
|
||||
|
||||
currentDest = reachability.predecessors[currentDest];
|
||||
}
|
||||
}
|
||||
|
||||
logAi->error("We should either detect that hexes are unreachable or make a move!");
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
|
||||
bool BattleEvaluator::canCastSpell()
|
||||
@ -391,7 +477,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
vstd::erase_if(possibleSpells, [](const CSpell *s)
|
||||
{
|
||||
return spellType(s) != SpellTypes::BATTLE || s->getTargetType() == spells::AimType::LOCATION;
|
||||
return spellType(s) != SpellTypes::BATTLE;
|
||||
});
|
||||
|
||||
LOGFL("I know how %d of them works.", possibleSpells.size());
|
||||
@ -402,9 +488,6 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
{
|
||||
spells::BattleCast temp(cb->getBattle(battleID).get(), hero, spells::Mode::HERO, spell);
|
||||
|
||||
if(spell->getTargetType() == spells::AimType::LOCATION)
|
||||
continue;
|
||||
|
||||
const bool FAST = true;
|
||||
|
||||
for(auto & target : temp.findPotentialTargets(FAST))
|
||||
@ -573,7 +656,15 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
auto & ps = possibleCasts[i];
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
if(ps.dest.empty())
|
||||
logAi->trace("Evaluating %s", ps.spell->getNameTranslated());
|
||||
else
|
||||
{
|
||||
auto psFirst = ps.dest.front();
|
||||
auto strWhere = psFirst.unitValue ? psFirst.unitValue->getDescription() : std::to_string(psFirst.hexValue.hex);
|
||||
|
||||
logAi->trace("Evaluating %s at %s", ps.spell->getNameTranslated(), strWhere);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto state = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
@ -591,38 +682,56 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
DamageCache safeCopy = damageCache;
|
||||
DamageCache innerCache(&safeCopy);
|
||||
|
||||
innerCache.buildDamageCache(state, side);
|
||||
|
||||
if(needFullEval || !cachedAttack)
|
||||
if(cachedAttack.ap && cachedAttack.waited)
|
||||
{
|
||||
state->makeWait(activeStack);
|
||||
}
|
||||
|
||||
if(needFullEval || !cachedAttack.ap)
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Full evaluation is started due to stack speed affected.");
|
||||
#endif
|
||||
|
||||
PotentialTargets innerTargets(activeStack, innerCache, state);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio, simulationTurnsCount);
|
||||
|
||||
innerEvaluator.updateReachabilityMap(state);
|
||||
|
||||
auto moveTarget = innerEvaluator.findMoveTowardsUnreachable(activeStack, innerTargets, innerCache, state);
|
||||
|
||||
if(!innerTargets.possibleAttacks.empty())
|
||||
{
|
||||
innerEvaluator.updateReachabilityMap(state);
|
||||
|
||||
auto newStackAction = innerEvaluator.findBestTarget(activeStack, innerTargets, innerCache, state);
|
||||
|
||||
ps.value = newStackAction.score;
|
||||
ps.value = std::max(moveTarget.score, newStackAction.score);
|
||||
}
|
||||
else
|
||||
{
|
||||
ps.value = 0;
|
||||
ps.value = moveTarget.score;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ps.value = scoreEvaluator.evaluateExchange(*cachedAttack, 0, *targets, innerCache, state);
|
||||
auto updatedAttacker = state->getForUpdate(cachedAttack.ap->attack.attacker->unitId());
|
||||
auto updatedDefender = state->getForUpdate(cachedAttack.ap->attack.defender->unitId());
|
||||
auto updatedBai = BattleAttackInfo(
|
||||
updatedAttacker.get(),
|
||||
updatedDefender.get(),
|
||||
cachedAttack.ap->attack.chargeDistance,
|
||||
cachedAttack.ap->attack.shooting);
|
||||
|
||||
auto updatedAttack = AttackPossibility::evaluate(updatedBai, cachedAttack.ap->from, innerCache, state);
|
||||
|
||||
ps.value = scoreEvaluator.evaluateExchange(updatedAttack, cachedAttack.turn, *targets, innerCache, state);
|
||||
}
|
||||
|
||||
for(const auto & unit : allUnits)
|
||||
{
|
||||
if (!unit->isValidTarget())
|
||||
if(!unit->isValidTarget(true))
|
||||
continue;
|
||||
|
||||
auto newHealth = unit->getAvailableHealth();
|
||||
@ -645,13 +754,18 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
if(ourUnit * goodEffect == 1)
|
||||
{
|
||||
if(ourUnit && goodEffect && (unit->isClone() || unit->isGhost()))
|
||||
auto isMagical = state->getForUpdate(unit->unitId())->summoned
|
||||
|| unit->isClone()
|
||||
|| unit->isGhost();
|
||||
|
||||
if(ourUnit && goodEffect && isMagical)
|
||||
continue;
|
||||
|
||||
ps.value += dpsReduce * scoreEvaluator.getPositiveEffectMultiplier();
|
||||
}
|
||||
else
|
||||
ps.value -= dpsReduce * scoreEvaluator.getNegativeEffectMultiplier();
|
||||
// discourage AI making collateral damage with spells
|
||||
ps.value -= 4 * dpsReduce * scoreEvaluator.getNegativeEffectMultiplier();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace(
|
||||
@ -662,6 +776,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Total score: %2f", ps.value);
|
||||
#endif
|
||||
@ -672,13 +787,12 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
|
||||
LOGFL("Evaluation took %d ms", timer.getDiff());
|
||||
|
||||
auto pscValue = [](const PossibleSpellcast &ps) -> float
|
||||
auto castToPerform = *vstd::maxElementByFun(possibleCasts, [](const PossibleSpellcast & ps) -> float
|
||||
{
|
||||
return ps.value;
|
||||
};
|
||||
auto castToPerform = *vstd::maxElementByFun(possibleCasts, pscValue);
|
||||
});
|
||||
|
||||
if(castToPerform.value > cachedScore)
|
||||
if(castToPerform.value > cachedAttack.score && !vstd::isAlmostEqual(castToPerform.value, cachedAttack.score))
|
||||
{
|
||||
LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
|
||||
BattleAction spellcast;
|
||||
@ -686,7 +800,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
spellcast.spell = castToPerform.spell->id;
|
||||
spellcast.setTarget(castToPerform.dest);
|
||||
spellcast.side = side;
|
||||
spellcast.stackNumber = (!side) ? -1 : -2;
|
||||
spellcast.stackNumber = -1;
|
||||
cb->battleMakeSpellAction(battleID, spellcast);
|
||||
activeActionMade = true;
|
||||
|
||||
|
@ -22,6 +22,14 @@ VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class EnemyInfo;
|
||||
|
||||
struct CachedAttack
|
||||
{
|
||||
std::optional<AttackPossibility> ap;
|
||||
float score = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
uint8_t turn = 255;
|
||||
bool waited = false;
|
||||
};
|
||||
|
||||
class BattleEvaluator
|
||||
{
|
||||
std::unique_ptr<PotentialTargets> targets;
|
||||
@ -30,23 +38,24 @@ class BattleEvaluator
|
||||
std::shared_ptr<CBattleCallback> cb;
|
||||
std::shared_ptr<Environment> env;
|
||||
bool activeActionMade = false;
|
||||
std::optional<AttackPossibility> cachedAttack;
|
||||
CachedAttack cachedAttack;
|
||||
PlayerColor playerID;
|
||||
BattleID battleID;
|
||||
int side;
|
||||
float cachedScore;
|
||||
BattleSide side;
|
||||
DamageCache damageCache;
|
||||
float strengthRatio;
|
||||
int simulationTurnsCount;
|
||||
|
||||
public:
|
||||
BattleAction selectStackAction(const CStack * stack);
|
||||
bool attemptCastingSpell(const CStack * stack);
|
||||
bool canCastSpell();
|
||||
std::optional<PossibleSpellcast> findBestCreatureSpell(const CStack * stack);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes);
|
||||
BattleAction goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes, const PotentialTargets & targets);
|
||||
std::vector<BattleHex> getBrokenWallMoatHexes() const;
|
||||
void evaluateCreatureSpellcast(const CStack * stack, PossibleSpellcast & ps); //for offensive damaging spells only
|
||||
void print(const std::string & text) const;
|
||||
BattleAction moveOrAttack(const CStack * stack, BattleHex hex, const PotentialTargets & targets);
|
||||
|
||||
BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
@ -54,16 +63,9 @@ public:
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
int side,
|
||||
float strengthRatio)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), strengthRatio(strengthRatio), battleID(battleID)
|
||||
{
|
||||
hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
|
||||
damageCache.buildDamageCache(hb, side);
|
||||
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount);
|
||||
|
||||
BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
@ -73,11 +75,7 @@ public:
|
||||
const battle::Unit * activeStack,
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
int side,
|
||||
float strengthRatio)
|
||||
:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb), damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID)
|
||||
{
|
||||
targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
BattleSide side,
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount);
|
||||
};
|
||||
|
@ -18,7 +18,7 @@ AttackerValue::AttackerValue()
|
||||
}
|
||||
|
||||
MoveTarget::MoveTarget()
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE), scorePerTurn(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
: positions(), cachedAttack(), score(EvaluationResult::INEFFECTIVE_SCORE)
|
||||
{
|
||||
turnsToRich = 1;
|
||||
}
|
||||
@ -28,102 +28,97 @@ float BattleExchangeVariant::trackAttack(
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
DamageCache & damageCache)
|
||||
{
|
||||
if(!ap.attackerState)
|
||||
{
|
||||
logAi->trace("Skipping fake ap attack");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto attacker = hb->getForUpdate(ap.attack.attacker->unitId());
|
||||
|
||||
const std::string cachingStringBlocksRetaliation = "type_BLOCKS_RETALIATION";
|
||||
static const auto selectorBlocksRetaliation = Selector::type()(BonusType::BLOCKS_RETALIATION);
|
||||
const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
|
||||
|
||||
float attackValue = 0;
|
||||
float attackValue = ap.attackValue();
|
||||
auto affectedUnits = ap.affectedUnits;
|
||||
|
||||
dpsScore.ourDamageReduce += ap.attackerDamageReduce + ap.collateralDamageReduce;
|
||||
dpsScore.enemyDamageReduce += ap.defenderDamageReduce + ap.shootersBlockedDmg;
|
||||
attackerValue[attacker->unitId()].value = attackValue;
|
||||
|
||||
affectedUnits.push_back(ap.attackerState);
|
||||
|
||||
for(auto affectedUnit : affectedUnits)
|
||||
{
|
||||
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
||||
auto damageDealt = unitToUpdate->getAvailableHealth() - affectedUnit->getAvailableHealth();
|
||||
|
||||
if(damageDealt > 0)
|
||||
{
|
||||
unitToUpdate->damage(damageDealt);
|
||||
}
|
||||
|
||||
if(unitToUpdate->unitSide() == attacker->unitSide())
|
||||
{
|
||||
if(unitToUpdate->unitId() == attacker->unitId())
|
||||
{
|
||||
auto defender = hb->getForUpdate(ap.attack.defender->unitId());
|
||||
|
||||
if(!defender->alive() || counterAttacksBlocked || ap.attack.shooting || !defender->ableToRetaliate())
|
||||
continue;
|
||||
|
||||
auto retaliationDamage = damageCache.getDamage(defender.get(), unitToUpdate.get(), hb);
|
||||
auto attackerDamageReduce = AttackPossibility::calculateDamageReduce(defender.get(), unitToUpdate.get(), retaliationDamage, damageCache, hb);
|
||||
|
||||
attackValue -= attackerDamageReduce;
|
||||
dpsScore.ourDamageReduce += attackerDamageReduce;
|
||||
attackerValue[unitToUpdate->unitId()].isRetaliated = true;
|
||||
|
||||
unitToUpdate->damage(retaliationDamage);
|
||||
defender->afterAttack(false, true);
|
||||
unitToUpdate->afterAttack(ap.attack.shooting, false);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap retaliation, %s, dps: %2f, score: %2f",
|
||||
defender->getDescription(),
|
||||
unitToUpdate->getDescription(),
|
||||
"%s -> %s, ap retaliation, %s, dps: %lld",
|
||||
hb->getForUpdate(ap.attack.defender->unitId())->getDescription(),
|
||||
ap.attack.attacker->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
retaliationDamage,
|
||||
attackerDamageReduce);
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
auto collateralDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
||||
auto collateralDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), collateralDamage, damageCache, hb);
|
||||
|
||||
attackValue -= collateralDamageReduce;
|
||||
dpsScore.ourDamageReduce += collateralDamageReduce;
|
||||
|
||||
unitToUpdate->damage(collateralDamage);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap collateral, %s, dps: %2f, score: %2f",
|
||||
attacker->getDescription(),
|
||||
"%s, ap collateral, dps: %lld",
|
||||
unitToUpdate->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
collateralDamage,
|
||||
collateralDamageReduce);
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t attackDamage = damageCache.getDamage(attacker.get(), unitToUpdate.get(), hb);
|
||||
float defenderDamageReduce = AttackPossibility::calculateDamageReduce(attacker.get(), unitToUpdate.get(), attackDamage, damageCache, hb);
|
||||
|
||||
attackValue += defenderDamageReduce;
|
||||
dpsScore.enemyDamageReduce += defenderDamageReduce;
|
||||
attackerValue[attacker->unitId()].value += defenderDamageReduce;
|
||||
|
||||
unitToUpdate->damage(attackDamage);
|
||||
if(unitToUpdate->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
if(unitToUpdate->ableToRetaliate() && !affectedUnit->ableToRetaliate())
|
||||
{
|
||||
unitToUpdate->afterAttack(ap.attack.shooting, true);
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap attack, %s, dps: %2f, score: %2f",
|
||||
"%s -> %s, ap attack, %s, dps: %lld",
|
||||
attacker->getDescription(),
|
||||
unitToUpdate->getDescription(),
|
||||
ap.attack.defender->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
attackDamage,
|
||||
defenderDamageReduce);
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s, ap enemy collateral, dps: %lld",
|
||||
unitToUpdate->getDescription(),
|
||||
damageDealt);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("ap shooters blocking: %lld", ap.shootersBlockedDmg);
|
||||
logAi->trace(
|
||||
"ap score: our: %2f, enemy: %2f, collateral: %2f, blocked: %2f",
|
||||
ap.attackerDamageReduce,
|
||||
ap.defenderDamageReduce,
|
||||
ap.collateralDamageReduce,
|
||||
ap.shootersBlockedDmg);
|
||||
#endif
|
||||
|
||||
attackValue += ap.shootersBlockedDmg;
|
||||
dpsScore.enemyDamageReduce += ap.shootersBlockedDmg;
|
||||
attacker->afterAttack(ap.attack.shooting, false);
|
||||
|
||||
return attackValue;
|
||||
}
|
||||
|
||||
@ -230,8 +225,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
|
||||
auto hbWaited = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||
|
||||
hbWaited->getForUpdate(activeStack->unitId())->waiting = true;
|
||||
hbWaited->getForUpdate(activeStack->unitId())->waitedThisTurn = true;
|
||||
hbWaited->makeWait(activeStack);
|
||||
|
||||
updateReachabilityMap(hbWaited);
|
||||
|
||||
@ -259,6 +253,7 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
if(result.bestAttack.attack.shooting
|
||||
&& !result.bestAttack.defenderDead
|
||||
&& !activeStack->waited()
|
||||
&& hb->battleHasShootingPenalty(activeStack, result.bestAttack.dest))
|
||||
{
|
||||
@ -269,8 +264,9 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
for(auto & ap : targets.possibleAttacks)
|
||||
{
|
||||
float score = evaluateExchange(ap, 0, targets, damageCache, hb);
|
||||
bool sameScoreButWaited = vstd::isAlmostEqual(score, result.score) && result.wait;
|
||||
|
||||
if(score > result.score || (vstd::isAlmostEqual(score, result.score) && result.wait))
|
||||
if(score > result.score || sameScoreButWaited)
|
||||
{
|
||||
result.score = score;
|
||||
result.bestAttack = ap;
|
||||
@ -285,6 +281,36 @@ EvaluationResult BattleExchangeEvaluator::findBestTarget(
|
||||
return result;
|
||||
}
|
||||
|
||||
ReachabilityInfo getReachabilityWithEnemyBypass(
|
||||
const battle::Unit * activeStack,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> state)
|
||||
{
|
||||
ReachabilityInfo::Parameters params(activeStack, activeStack->getPosition());
|
||||
|
||||
if(!params.flying)
|
||||
{
|
||||
for(const auto * unit : state->battleAliveUnits())
|
||||
{
|
||||
if(unit->unitSide() == activeStack->unitSide())
|
||||
continue;
|
||||
|
||||
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
||||
auto turnsToKill = unit->getAvailableHealth() / std::max(dmg, (int64_t)1);
|
||||
|
||||
vstd::amin(turnsToKill, 100);
|
||||
|
||||
for(auto & hex : unit->getHexes())
|
||||
if(hex.isAvailable()) //towers can have <0 pos; we don't also want to overwrite side columns
|
||||
params.destructibleEnemyTurns[hex] = turnsToKill * unit->getMovementRange();
|
||||
}
|
||||
|
||||
params.bypassEnemyStacks = true;
|
||||
}
|
||||
|
||||
return state->getReachability(params);
|
||||
}
|
||||
|
||||
MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
const battle::Unit * activeStack,
|
||||
PotentialTargets & targets,
|
||||
@ -294,6 +320,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
MoveTarget result;
|
||||
BattleExchangeVariant ev;
|
||||
|
||||
logAi->trace("Find move towards unreachable. Enemies count %d", targets.unreachableEnemies.size());
|
||||
|
||||
if(targets.unreachableEnemies.empty())
|
||||
return result;
|
||||
|
||||
@ -304,17 +332,17 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
|
||||
updateReachabilityMap(hb);
|
||||
|
||||
auto dists = cb->getReachability(activeStack);
|
||||
auto dists = getReachabilityWithEnemyBypass(activeStack, damageCache, hb);
|
||||
auto flying = activeStack->hasBonusOfType(BonusType::FLYING);
|
||||
|
||||
for(const battle::Unit * enemy : targets.unreachableEnemies)
|
||||
{
|
||||
std::vector<const battle::Unit *> adjacentStacks = getAdjacentUnits(enemy);
|
||||
auto closestStack = *vstd::minElementByFun(adjacentStacks, [&](const battle::Unit * u) -> int64_t
|
||||
{
|
||||
return dists.distToNearestNeighbour(activeStack, u) * 100000 - activeStack->getTotalHealth();
|
||||
});
|
||||
logAi->trace(
|
||||
"Checking movement towards %d of %s",
|
||||
enemy->getCount(),
|
||||
enemy->creatureId().toCreature()->getNameSingularTranslated());
|
||||
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, closestStack);
|
||||
auto distance = dists.distToNearestNeighbour(activeStack, enemy);
|
||||
|
||||
if(distance >= GameConstants::BFIELD_SIZE)
|
||||
continue;
|
||||
@ -323,30 +351,87 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(
|
||||
continue;
|
||||
|
||||
auto turnsToRich = (distance - 1) / speed + 1;
|
||||
auto hexes = closestStack->getSurroundingHexes();
|
||||
auto enemySpeed = closestStack->getMovementRange();
|
||||
auto hexes = enemy->getSurroundingHexes();
|
||||
auto enemySpeed = enemy->getMovementRange();
|
||||
auto speedRatio = speed / static_cast<float>(enemySpeed);
|
||||
auto multiplier = speedRatio > 1 ? 1 : speedRatio;
|
||||
|
||||
if(enemy->canShoot())
|
||||
multiplier *= 1.5f;
|
||||
|
||||
for(auto hex : hexes)
|
||||
for(auto & hex : hexes)
|
||||
{
|
||||
// FIXME: provide distance info for Jousting bonus
|
||||
auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
|
||||
auto bai = BattleAttackInfo(activeStack, enemy, 0, cb->battleCanShoot(activeStack));
|
||||
auto attack = AttackPossibility::evaluate(bai, hex, damageCache, hb);
|
||||
|
||||
attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure
|
||||
|
||||
auto score = calculateExchange(attack, turnsToRich, targets, damageCache, hb);
|
||||
auto scorePerTurn = BattleScore(score.enemyDamageReduce * std::sqrt(multiplier / turnsToRich), score.ourDamageReduce);
|
||||
|
||||
if(result.scorePerTurn < scoreValue(scorePerTurn))
|
||||
score.enemyDamageReduce *= multiplier;
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Multiplier: %f, turns: %d, current score %f, new score %f", multiplier, turnsToRich, result.score, scoreValue(score));
|
||||
#endif
|
||||
|
||||
if(result.score < scoreValue(score)
|
||||
|| (result.turnsToRich > turnsToRich && vstd::isAlmostEqual(result.score, scoreValue(score))))
|
||||
{
|
||||
result.scorePerTurn = scoreValue(scorePerTurn);
|
||||
result.score = scoreValue(score);
|
||||
result.positions = closestStack->getAttackableHexes(activeStack);
|
||||
result.positions.clear();
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score");
|
||||
#endif
|
||||
|
||||
for(const BattleHex & initialEnemyHex : enemy->getAttackableHexes(activeStack))
|
||||
{
|
||||
BattleHex enemyHex = initialEnemyHex;
|
||||
|
||||
while(!flying && dists.distances[enemyHex] > speed && dists.predecessors.at(enemyHex).isValid())
|
||||
{
|
||||
enemyHex = dists.predecessors.at(enemyHex);
|
||||
|
||||
if(dists.accessibility[enemyHex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto defenderToBypass = hb->battleGetUnitByPos(enemyHex);
|
||||
|
||||
if(defenderToBypass)
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("Found target to bypass at %d", enemyHex.hex);
|
||||
#endif
|
||||
|
||||
auto attackHex = dists.predecessors[enemyHex];
|
||||
auto baiBypass = BattleAttackInfo(activeStack, defenderToBypass, 0, cb->battleCanShoot(activeStack));
|
||||
auto attackBypass = AttackPossibility::evaluate(baiBypass, attackHex, damageCache, hb);
|
||||
|
||||
auto adjacentStacks = getAdjacentUnits(enemy);
|
||||
|
||||
adjacentStacks.push_back(defenderToBypass);
|
||||
vstd::removeDuplicates(adjacentStacks);
|
||||
|
||||
auto bypassScore = calculateExchange(
|
||||
attackBypass,
|
||||
dists.distances[attackHex],
|
||||
targets,
|
||||
damageCache,
|
||||
hb,
|
||||
adjacentStacks);
|
||||
|
||||
if(scoreValue(bypassScore) > result.score)
|
||||
{
|
||||
result.score = scoreValue(bypassScore);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL >= 1
|
||||
logAi->trace("New high score after bypass %f", scoreValue(bypassScore));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.positions.push_back(enemyHex);
|
||||
}
|
||||
|
||||
result.cachedAttack = attack;
|
||||
result.turnsToRich = turnsToRich;
|
||||
}
|
||||
@ -390,7 +475,8 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
ReachabilityData result;
|
||||
|
||||
@ -398,13 +484,29 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
if(!ap.attack.shooting) hexes.push_back(ap.from);
|
||||
|
||||
std::vector<const battle::Unit *> allReachableUnits;
|
||||
std::vector<const battle::Unit *> allReachableUnits = additionalUnits;
|
||||
|
||||
for(auto hex : hexes)
|
||||
{
|
||||
vstd::concatenate(allReachableUnits, turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex));
|
||||
}
|
||||
|
||||
if(!ap.attack.attacker->isTurret())
|
||||
{
|
||||
for(auto hex : ap.attack.attacker->getHexes())
|
||||
{
|
||||
auto unitsReachingAttacker = turn == 0 ? reachabilityMap.at(hex) : getOneTurnReachableUnits(turn, hex);
|
||||
for(auto unit : unitsReachingAttacker)
|
||||
{
|
||||
if(unit->unitSide() != ap.attack.attacker->unitSide())
|
||||
{
|
||||
allReachableUnits.push_back(unit);
|
||||
result.enemyUnitsReachingAttacker.insert(unit->unitId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vstd::removeDuplicates(allReachableUnits);
|
||||
|
||||
auto copy = allReachableUnits;
|
||||
@ -440,7 +542,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
for(auto unit : allReachableUnits)
|
||||
{
|
||||
auto accessible = !unit->canShoot();
|
||||
auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
|
||||
|
||||
if(!accessible)
|
||||
{
|
||||
@ -464,14 +566,14 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
for(auto unit : turnOrder[turn])
|
||||
{
|
||||
if(vstd::contains(allReachableUnits, unit))
|
||||
result.units.push_back(unit);
|
||||
}
|
||||
result.units[turn].push_back(unit);
|
||||
}
|
||||
|
||||
vstd::erase_if(result.units, [&](const battle::Unit * u) -> bool
|
||||
vstd::erase_if(result.units[turn], [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -502,13 +604,14 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits) const
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle exchange at %d", ap.attack.shooting ? ap.dest.hex : ap.from.hex);
|
||||
#endif
|
||||
|
||||
if(cb->battleGetMySide() == BattlePerspective::LEFT_SIDE
|
||||
if(cb->battleGetMySide() == BattleSide::LEFT_SIDE
|
||||
&& cb->battleGetGateState() == EGateState::BLOCKED
|
||||
&& ap.attack.defender->coversPos(BattleHex::GATE_BRIDGE))
|
||||
{
|
||||
@ -521,7 +624,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
if(hb->battleGetUnitByID(ap.attack.defender->unitId())->alive())
|
||||
enemyStacks.push_back(ap.attack.defender);
|
||||
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb);
|
||||
ReachabilityData exchangeUnits = getExchangeUnits(ap, turn, targets, hb, additionalUnits);
|
||||
|
||||
if(exchangeUnits.units.empty())
|
||||
{
|
||||
@ -531,7 +634,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
auto exchangeBattle = std::make_shared<HypotheticBattle>(env.get(), hb);
|
||||
BattleExchangeVariant v;
|
||||
|
||||
for(auto unit : exchangeUnits.units)
|
||||
for(int exchangeTurn = 0; exchangeTurn < exchangeUnits.units.size(); exchangeTurn++)
|
||||
{
|
||||
for(auto unit : exchangeUnits.units.at(exchangeTurn))
|
||||
{
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
@ -549,6 +654,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto melleeAttackers = ourStacks;
|
||||
|
||||
@ -560,18 +666,43 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
|
||||
bool canUseAp = true;
|
||||
|
||||
for(auto activeUnit : exchangeUnits.units)
|
||||
std::set<uint32_t> blockedShooters;
|
||||
|
||||
int totalTurnsCount = simulationTurnsCount >= turn + turnOrder.size()
|
||||
? simulationTurnsCount
|
||||
: turn + turnOrder.size();
|
||||
|
||||
for(int exchangeTurn = 0; exchangeTurn < simulationTurnsCount; exchangeTurn++)
|
||||
{
|
||||
bool isMovingTurm = exchangeTurn < turn;
|
||||
int queueTurn = exchangeTurn >= exchangeUnits.units.size()
|
||||
? exchangeUnits.units.size() - 1
|
||||
: exchangeTurn;
|
||||
|
||||
for(auto activeUnit : exchangeUnits.units.at(queueTurn))
|
||||
{
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get())
|
||||
&& !vstd::contains(blockedShooters, attacker->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace( "Attacker is dead");
|
||||
logAi->trace("Attacker is dead");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if(isMovingTurm && !shooting
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Attacker is moving");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
@ -581,6 +712,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector for %s", attacker->getDescription());
|
||||
#endif
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
@ -593,7 +727,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
@ -607,6 +741,17 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!isOur
|
||||
&& exchangeTurn == 0
|
||||
&& exchangeUnits.units.at(exchangeTurn).at(0)->unitId() != ap.attack.attacker->unitId()
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->unitId() == ap.attack.attacker->unitId();
|
||||
});
|
||||
}
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
@ -621,7 +766,7 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if (!u->getPosition().isValid())
|
||||
if(!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
@ -646,7 +791,6 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
}
|
||||
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
@ -665,6 +809,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
}
|
||||
}
|
||||
|
||||
if(!shooting)
|
||||
blockedShooters.insert(defender->unitId());
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
@ -678,6 +825,9 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
});
|
||||
}
|
||||
|
||||
exchangeBattle->nextRound();
|
||||
}
|
||||
|
||||
// avoid blocking path for stronger stack by weaker stack
|
||||
// the method checks if all stacks can be placed around enemy
|
||||
std::map<BattleHex, battle::Units> reachabilityMap;
|
||||
@ -687,11 +837,28 @@ BattleScore BattleExchangeEvaluator::calculateExchange(
|
||||
for(auto hex : hexes)
|
||||
reachabilityMap[hex] = getOneTurnReachableUnits(turn, hex);
|
||||
|
||||
auto score = v.getScore();
|
||||
|
||||
if(simulationTurnsCount < totalTurnsCount)
|
||||
{
|
||||
float scalingRatio = simulationTurnsCount / static_cast<float>(totalTurnsCount);
|
||||
|
||||
score.enemyDamageReduce *= scalingRatio;
|
||||
score.ourDamageReduce *= scalingRatio;
|
||||
}
|
||||
|
||||
if(turn > 0)
|
||||
{
|
||||
auto turnMultiplier = 1 - std::min(0.2, 0.05 * turn);
|
||||
|
||||
score.enemyDamageReduce *= turnMultiplier;
|
||||
}
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", v.getScore().enemyDamageReduce, v.getScore().ourDamageReduce);
|
||||
logAi->trace("Exchange score: enemy: %2f, our -%2f", score.enemyDamageReduce, score.ourDamageReduce);
|
||||
#endif
|
||||
|
||||
return v.getScore();
|
||||
return score;
|
||||
}
|
||||
|
||||
bool BattleExchangeEvaluator::canBeHitThisTurn(const AttackPossibility & ap)
|
||||
@ -739,7 +906,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
|
||||
{
|
||||
std::vector<const battle::Unit *> result;
|
||||
|
||||
for(int i = 0; i < turnOrder.size(); i++, turn++)
|
||||
for(int i = 0; i < turnOrder.size(); i++)
|
||||
{
|
||||
auto & turnQueue = turnOrder[i];
|
||||
HypotheticBattle turnBattle(env.get(), cb);
|
||||
@ -764,7 +931,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
|
||||
|
||||
ReachabilityInfo unitReachability = reachabilityIter != reachabilityCache.end() ? reachabilityIter->second : turnBattle.getReachability(unit);
|
||||
|
||||
bool reachable = unitReachability.distances[hex] <= radius;
|
||||
bool reachable = unitReachability.distances.at(hex) <= radius;
|
||||
|
||||
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
@ -774,7 +941,7 @@ std::vector<const battle::Unit *> BattleExchangeEvaluator::getOneTurnReachableUn
|
||||
{
|
||||
for(BattleHex neighbor : hex.neighbouringTiles())
|
||||
{
|
||||
reachable = unitReachability.distances[neighbor] <= radius;
|
||||
reachable = unitReachability.distances.at(neighbor) <= radius;
|
||||
|
||||
if(reachable) break;
|
||||
}
|
||||
@ -824,7 +991,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
||||
for(BattleHex hex = BattleHex::TOP_LEFT; hex.isValid(); hex = hex + 1)
|
||||
{
|
||||
bool enemyUnit = false;
|
||||
bool reachable = unitReachability.distances[hex] <= unitSpeed;
|
||||
bool reachable = unitReachability.distances.at(hex) <= unitSpeed;
|
||||
|
||||
if(!reachable && unitReachability.accessibility[hex] == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
@ -836,7 +1003,7 @@ bool BattleExchangeEvaluator::checkPositionBlocksOurStacks(HypotheticBattle & hb
|
||||
|
||||
for(BattleHex neighbor : hex.neighbouringTiles())
|
||||
{
|
||||
reachable = unitReachability.distances[neighbor] <= unitSpeed;
|
||||
reachable = unitReachability.distances.at(neighbor) <= unitSpeed;
|
||||
|
||||
if(reachable) break;
|
||||
}
|
||||
|
@ -54,7 +54,6 @@ struct AttackerValue
|
||||
struct MoveTarget
|
||||
{
|
||||
float score;
|
||||
float scorePerTurn;
|
||||
std::vector<BattleHex> positions;
|
||||
std::optional<AttackPossibility> cachedAttack;
|
||||
uint8_t turnsToRich;
|
||||
@ -64,7 +63,7 @@ struct MoveTarget
|
||||
|
||||
struct EvaluationResult
|
||||
{
|
||||
static const int64_t INEFFECTIVE_SCORE = -10000;
|
||||
static const int64_t INEFFECTIVE_SCORE = -100000000;
|
||||
|
||||
AttackPossibility bestAttack;
|
||||
MoveTarget bestMove;
|
||||
@ -113,13 +112,15 @@ private:
|
||||
|
||||
struct ReachabilityData
|
||||
{
|
||||
std::vector<const battle::Unit *> units;
|
||||
std::map<int, std::vector<const battle::Unit *>> units;
|
||||
|
||||
// shooters which are within mellee attack and mellee units
|
||||
std::vector<const battle::Unit *> melleeAccessible;
|
||||
|
||||
// far shooters
|
||||
std::vector<const battle::Unit *> shooters;
|
||||
|
||||
std::set<uint32_t> enemyUnitsReachingAttacker;
|
||||
};
|
||||
|
||||
class BattleExchangeEvaluator
|
||||
@ -131,6 +132,7 @@ private:
|
||||
std::map<BattleHex, std::vector<const battle::Unit *>> reachabilityMap;
|
||||
std::vector<battle::Units> turnOrder;
|
||||
float negativeEffectMultiplier;
|
||||
int simulationTurnsCount;
|
||||
|
||||
float scoreValue(const BattleScore & score) const;
|
||||
|
||||
@ -139,7 +141,8 @@ private:
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
DamageCache & damageCache,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool canBeHitThisTurn(const AttackPossibility & ap);
|
||||
|
||||
@ -147,8 +150,9 @@ public:
|
||||
BattleExchangeEvaluator(
|
||||
std::shared_ptr<CBattleInfoCallback> cb,
|
||||
std::shared_ptr<Environment> env,
|
||||
float strengthRatio): cb(cb), env(env) {
|
||||
negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio;
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount): cb(cb), env(env), simulationTurnsCount(simulationTurnsCount){
|
||||
negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio * strengthRatio;
|
||||
}
|
||||
|
||||
EvaluationResult findBestTarget(
|
||||
@ -171,7 +175,8 @@ public:
|
||||
const AttackPossibility & ap,
|
||||
uint8_t turn,
|
||||
PotentialTargets & targets,
|
||||
std::shared_ptr<HypotheticBattle> hb) const;
|
||||
std::shared_ptr<HypotheticBattle> hb,
|
||||
std::vector<const battle::Unit *> additionalUnits = {}) const;
|
||||
|
||||
bool checkPositionBlocksOurStacks(HypotheticBattle & hb, const battle::Unit * unit, BattleHex position);
|
||||
|
||||
|
@ -37,11 +37,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(BattleAI PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(BattleAI PRIVATE vcmi TBB::tbb)
|
||||
target_link_libraries(BattleAI PRIVATE vcmi)
|
||||
|
||||
vcmi_set_output_dir(BattleAI "AI")
|
||||
enable_pch(BattleAI)
|
||||
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
install(IMPORTED_RUNTIME_ARTIFACTS TBB::tbb LIBRARY DESTINATION ${LIB_DIR}) # CMake 3.21+
|
||||
endif()
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "StdInc.h"
|
||||
#include "PotentialTargets.h"
|
||||
#include "../../lib/CStack.h"//todo: remove
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
||||
PotentialTargets::PotentialTargets(
|
||||
const battle::Unit * attacker,
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include <vcmi/events/EventBus.h>
|
||||
|
||||
#include "../../lib/battle/BattleLayout.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/ScriptHandler.h"
|
||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||
@ -116,7 +117,7 @@ uint32_t StackWithBonuses::unitId() const
|
||||
return id;
|
||||
}
|
||||
|
||||
ui8 StackWithBonuses::unitSide() const
|
||||
BattleSide StackWithBonuses::unitSide() const
|
||||
{
|
||||
return side;
|
||||
}
|
||||
@ -132,10 +133,10 @@ SlotID StackWithBonuses::unitSlot() const
|
||||
}
|
||||
|
||||
TConstBonusListPtr StackWithBonuses::getAllBonuses(const CSelector & selector, const CSelector & limit,
|
||||
const CBonusSystemNode * root, const std::string & cachingStr) const
|
||||
const std::string & cachingStr) const
|
||||
{
|
||||
auto ret = std::make_shared<BonusList>();
|
||||
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, root, cachingStr);
|
||||
TConstBonusListPtr originalList = origBearer->getAllBonuses(selector, limit, cachingStr);
|
||||
|
||||
vstd::copy_if(*originalList, std::back_inserter(*ret), [this](const std::shared_ptr<Bonus> & b)
|
||||
{
|
||||
@ -467,7 +468,7 @@ int64_t HypotheticBattle::getActualDamage(const DamageRange & damage, int32_t at
|
||||
return (damage.min + damage.max) / 2;
|
||||
}
|
||||
|
||||
std::vector<SpellID> HypotheticBattle::getUsedSpells(ui8 side) const
|
||||
std::vector<SpellID> HypotheticBattle::getUsedSpells(BattleSide side) const
|
||||
{
|
||||
// TODO
|
||||
return {};
|
||||
@ -479,10 +480,9 @@ int3 HypotheticBattle::getLocation() const
|
||||
return int3(-1, -1, -1);
|
||||
}
|
||||
|
||||
bool HypotheticBattle::isCreatureBank() const
|
||||
BattleLayout HypotheticBattle::getLayout() const
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
return subject->getBattle()->getLayout();
|
||||
}
|
||||
|
||||
int64_t HypotheticBattle::getTreeVersion() const
|
||||
@ -502,10 +502,18 @@ ServerCallback * HypotheticBattle::getServerCallback()
|
||||
return serverCallback.get();
|
||||
}
|
||||
|
||||
void HypotheticBattle::makeWait(const battle::Unit * activeStack)
|
||||
{
|
||||
auto unit = getForUpdate(activeStack->unitId());
|
||||
|
||||
resetActiveUnit();
|
||||
unit->waiting = true;
|
||||
unit->waitedThisTurn = true;
|
||||
}
|
||||
|
||||
HypotheticBattle::HypotheticServerCallback::HypotheticServerCallback(HypotheticBattle * owner_)
|
||||
:owner(owner_)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HypotheticBattle::HypotheticServerCallback::complain(const std::string & problem)
|
||||
|
@ -24,7 +24,7 @@ class HypotheticBattle;
|
||||
class RNGStub final : public vstd::RNG
|
||||
{
|
||||
public:
|
||||
virtual int nextInt() override
|
||||
int nextInt() override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@ -85,13 +85,13 @@ public:
|
||||
int32_t unitBaseAmount() const override;
|
||||
|
||||
uint32_t unitId() const override;
|
||||
ui8 unitSide() const override;
|
||||
BattleSide unitSide() const override;
|
||||
PlayerColor unitOwner() const override;
|
||||
SlotID unitSlot() const override;
|
||||
|
||||
///IBonusBearer
|
||||
TConstBonusListPtr getAllBonuses(const CSelector & selector, const CSelector & limit,
|
||||
const CBonusSystemNode * root = nullptr, const std::string & cachingStr = "") const override;
|
||||
const std::string & cachingStr = "") const override;
|
||||
|
||||
int64_t getTreeVersion() const override;
|
||||
|
||||
@ -111,7 +111,7 @@ private:
|
||||
const CCreature * type;
|
||||
ui32 baseAmount;
|
||||
uint32_t id;
|
||||
ui8 side;
|
||||
BattleSide side;
|
||||
PlayerColor player;
|
||||
SlotID slot;
|
||||
};
|
||||
@ -158,12 +158,19 @@ public:
|
||||
uint32_t nextUnitId() const override;
|
||||
|
||||
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
std::vector<SpellID> getUsedSpells(ui8 side) const override;
|
||||
std::vector<SpellID> getUsedSpells(BattleSide side) const override;
|
||||
int3 getLocation() const override;
|
||||
bool isCreatureBank() const override;
|
||||
BattleLayout getLayout() const override;
|
||||
|
||||
int64_t getTreeVersion() const;
|
||||
|
||||
void makeWait(const battle::Unit * activeStack);
|
||||
|
||||
void resetActiveUnit()
|
||||
{
|
||||
activeUnitId = -1;
|
||||
}
|
||||
|
||||
#if SCRIPTING_ENABLED
|
||||
scripting::Pool * getContextPool() const override;
|
||||
#endif
|
||||
|
@ -8,10 +8,6 @@ else()
|
||||
option(FORCE_BUNDLED_FL "Force to use FuzzyLite included into VCMI's source tree" OFF)
|
||||
endif()
|
||||
|
||||
if(TBB_FOUND AND MSVC)
|
||||
install_vcpkg_imported_tgt(TBB::tbb)
|
||||
endif()
|
||||
|
||||
#FuzzyLite uses MSVC pragmas in headers, so, we need to disable -Wunknown-pragmas
|
||||
if(MINGW)
|
||||
add_compile_options(-Wno-unknown-pragmas)
|
||||
|
@ -18,11 +18,9 @@
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/serializer/CTypeList.h"
|
||||
#include "../../lib/serializer/BinarySerializer.h"
|
||||
#include "../../lib/serializer/BinaryDeserializer.h"
|
||||
#include "../../lib/networkPacks/PacksForClient.h"
|
||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||
#include "../../lib/networkPacks/PacksForServer.h"
|
||||
@ -570,6 +568,7 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
|
||||
LOG_TRACE(logAi);
|
||||
myCb = CB;
|
||||
cbc = CB;
|
||||
this->env = env;
|
||||
|
||||
NET_EVENT_HANDLER;
|
||||
playerID = *myCb->getPlayerID();
|
||||
@ -1148,7 +1147,7 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
|
||||
}
|
||||
}
|
||||
|
||||
void AIGateway::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
|
||||
void AIGateway::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, BattleSide side, bool replayAllowed)
|
||||
{
|
||||
NET_EVENT_HANDLER;
|
||||
assert(!playerID.isValidPlayer() || status.getBattle() == UPCOMING_BATTLE);
|
||||
@ -1490,7 +1489,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
|
||||
//TODO trade only as much as needed
|
||||
if (toGive) //don't try to sell 0 resources
|
||||
{
|
||||
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
|
||||
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
|
||||
acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
|
||||
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ public:
|
||||
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
|
||||
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
|
||||
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) 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;
|
||||
void battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
|
||||
|
||||
void makeTurn();
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
#include "../../lib/gameState/QuestInfo.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
|
||||
#include <vcmi/CreatureService.h>
|
||||
|
||||
|
@ -31,16 +31,14 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
|
||||
}
|
||||
}
|
||||
|
||||
BuildingID prefixes[] = {BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_FIRST};
|
||||
|
||||
for(int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++)
|
||||
for(int level = 0; level < developmentInfo.town->town->creatures.size(); level++)
|
||||
{
|
||||
logAi->trace("Checking dwelling level %d", level);
|
||||
BuildingInfo nextToBuild = BuildingInfo();
|
||||
|
||||
for(BuildingID prefix : prefixes)
|
||||
for(int upgradeIndex : {1, 0})
|
||||
{
|
||||
BuildingID building = BuildingID(prefix + level);
|
||||
BuildingID building = BuildingID(BuildingID::getDwellingFromLevel(level, upgradeIndex));
|
||||
|
||||
if(!vstd::contains(buildings, building))
|
||||
continue; // no such building in town
|
||||
@ -211,8 +209,8 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
|
||||
|
||||
if(BuildingID::DWELL_FIRST <= toBuild && toBuild <= BuildingID::DWELL_UP_LAST)
|
||||
{
|
||||
creatureLevel = (toBuild - BuildingID::DWELL_FIRST) % GameConstants::CREATURES_PER_TOWN;
|
||||
creatureUpgrade = (toBuild - BuildingID::DWELL_FIRST) / GameConstants::CREATURES_PER_TOWN;
|
||||
creatureLevel = BuildingID::getLevelFromDwelling(toBuild);
|
||||
creatureUpgrade = BuildingID::getUpgradedFromDwelling(toBuild);
|
||||
}
|
||||
else if(toBuild == BuildingID::HORDE_1 || toBuild == BuildingID::HORDE_1_UPGR)
|
||||
{
|
||||
@ -316,9 +314,7 @@ void BuildAnalyzer::updateDailyIncome()
|
||||
const CGMine* mine = dynamic_cast<const CGMine*>(obj);
|
||||
|
||||
if(mine)
|
||||
{
|
||||
dailyIncome[mine->producedResource.getNum()] += mine->producedQuantity;
|
||||
}
|
||||
dailyIncome += mine->dailyIncome();
|
||||
}
|
||||
|
||||
for(const CGTownInstance* town : towns)
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../pforeach.h"
|
||||
#include "../../../lib/CRandomGenerator.h"
|
||||
#include "../../../lib/logging/VisualLogger.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -24,6 +25,41 @@ double HitMapInfo::value() const
|
||||
return danger / std::sqrt(turn / 3.0f + 1);
|
||||
}
|
||||
|
||||
void logHitmap(PlayerColor playerID, DangerHitMapAnalyzer & data)
|
||||
{
|
||||
#if NKAI_TRACE_LEVEL >= 1
|
||||
logVisual->updateWithLock(playerID.toString() + ".danger.max", [&data](IVisualLogBuilder & b)
|
||||
{
|
||||
foreach_tile_pos([&b, &data](const int3 & pos)
|
||||
{
|
||||
auto & treat = data.getTileThreat(pos).maximumDanger;
|
||||
b.addText(pos, std::to_string(treat.danger));
|
||||
|
||||
if(treat.hero.validAndSet())
|
||||
{
|
||||
b.addText(pos, std::to_string(treat.turn));
|
||||
b.addText(pos, treat.hero->getNameTranslated());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
logVisual->updateWithLock(playerID.toString() + ".danger.fast", [&data](IVisualLogBuilder & b)
|
||||
{
|
||||
foreach_tile_pos([&b, &data](const int3 & pos)
|
||||
{
|
||||
auto & treat = data.getTileThreat(pos).fastestDanger;
|
||||
b.addText(pos, std::to_string(treat.danger));
|
||||
|
||||
if(treat.hero.validAndSet())
|
||||
{
|
||||
b.addText(pos, std::to_string(treat.turn));
|
||||
b.addText(pos, treat.hero->getNameTranslated());
|
||||
}
|
||||
});
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void DangerHitMapAnalyzer::updateHitMap()
|
||||
{
|
||||
if(hitMapUpToDate)
|
||||
@ -53,6 +89,14 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
|
||||
heroes[hero->tempOwner][hero] = HeroRole::MAIN;
|
||||
}
|
||||
|
||||
if(obj->ID == Obj::TOWN)
|
||||
{
|
||||
auto town = dynamic_cast<const CGTownInstance *>(obj);
|
||||
|
||||
if(town->garrisonHero)
|
||||
heroes[town->garrisonHero->tempOwner][town->garrisonHero] = HeroRole::MAIN;
|
||||
}
|
||||
}
|
||||
|
||||
auto ourTowns = cb->getTownsInfo();
|
||||
@ -144,6 +188,8 @@ void DangerHitMapAnalyzer::updateHitMap()
|
||||
}
|
||||
|
||||
logAi->trace("Danger hit map updated in %ld", timeElapsed(start));
|
||||
|
||||
logHitmap(ai->playerID, *this);
|
||||
}
|
||||
|
||||
void DangerHitMapAnalyzer::calculateTileOwners()
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "../Engine/Nullkiller.h"
|
||||
#include "../../../lib/mapObjects/MapObjects.h"
|
||||
#include "../../../lib/CHeroHandler.h"
|
||||
#include "../../../lib/GameSettings.h"
|
||||
#include "../../../lib/IGameSettings.h"
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
@ -196,8 +196,8 @@ bool HeroManager::heroCapReached() const
|
||||
|
||||
return heroCount >= ALLOWED_ROAMING_HEROES
|
||||
|| heroCount >= ai->settings->getMaxRoamingHeroes()
|
||||
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
|
||||
|| heroCount >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
|
||||
|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP)
|
||||
|| heroCount >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP);
|
||||
}
|
||||
|
||||
float HeroManager::getFightingStrengthCached(const CGHeroInstance * hero) const
|
||||
|
@ -212,7 +212,7 @@ void CaptureObjectsBehavior::decomposeObjects(
|
||||
vstd::concatenate(tasksLocal, getVisitGoals(paths, nullkiller, objToVisit, specificObjects));
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(sync); // FIXME: consider using tbb::parallel_reduce instead to avoid mutex overhead
|
||||
std::lock_guard lock(sync); // FIXME: consider using tbb::parallel_reduce instead to avoid mutex overhead
|
||||
vstd::concatenate(result, tasksLocal);
|
||||
});
|
||||
}
|
||||
|
@ -157,11 +157,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(Nullkiller PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite TBB::tbb)
|
||||
target_link_libraries(Nullkiller PUBLIC vcmi fuzzylite::fuzzylite)
|
||||
|
||||
vcmi_set_output_dir(Nullkiller "AI")
|
||||
enable_pch(Nullkiller)
|
||||
|
||||
if(APPLE_IOS AND NOT USING_CONAN)
|
||||
install(IMPORTED_RUNTIME_ARTIFACTS TBB::tbb LIBRARY DESTINATION ${LIB_DIR}) # CMake 3.21+
|
||||
endif()
|
||||
|
@ -208,12 +208,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c
|
||||
enemyFlyers->setValue(enemyStructure.flyers);
|
||||
enemySpeed->setValue(enemyStructure.maxSpeed);
|
||||
|
||||
bool bank = dynamic_cast<const CBank *>(enemy);
|
||||
if(bank)
|
||||
bankPresent->setValue(1);
|
||||
else
|
||||
bankPresent->setValue(0);
|
||||
|
||||
const CGTownInstance * fort = dynamic_cast<const CGTownInstance *>(enemy);
|
||||
if(fort)
|
||||
castleWalls->setValue(fort->fortLevel());
|
||||
|
@ -20,25 +20,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
|
||||
auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
|
||||
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
|
||||
ui64 totalStrength = 0;
|
||||
ui8 totalChance = 0;
|
||||
for(auto config : bankInfo->getPossibleGuards(bank->cb))
|
||||
{
|
||||
totalStrength += config.second.totalStrength * config.first;
|
||||
totalChance += config.first;
|
||||
}
|
||||
return totalStrength / std::max<ui8>(totalChance, 1); //avoid division by zero
|
||||
|
||||
}
|
||||
|
||||
ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards)
|
||||
{
|
||||
auto cb = ai->cb.get();
|
||||
@ -158,30 +139,14 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
return 0;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Obj::MONSTER:
|
||||
case Obj::GARRISON:
|
||||
case Obj::GARRISON2:
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
case Obj::PANDORAS_BOX:
|
||||
case Obj::CRYPT: //crypt
|
||||
case Obj::CREATURE_BANK: //crebank
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
case Obj::SHIPWRECK: //shipwreck
|
||||
case Obj::DERELICT_SHIP: //derelict ship
|
||||
default:
|
||||
{
|
||||
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
||||
if (a)
|
||||
return a->getArmyStrength();
|
||||
}
|
||||
case Obj::PYRAMID:
|
||||
{
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
}
|
||||
default:
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,12 +10,6 @@
|
||||
#pragma once
|
||||
#include "FuzzyEngines.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CBank;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
@ -30,8 +24,6 @@ private:
|
||||
public:
|
||||
FuzzyHelper(const Nullkiller * ai): ai(ai) {}
|
||||
|
||||
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
|
||||
|
||||
ui64 evaluateDanger(const CGObjectInstance * obj);
|
||||
ui64 evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards = true);
|
||||
};
|
||||
|
@ -118,36 +118,15 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
|
||||
return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
|
||||
}
|
||||
|
||||
TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||
{
|
||||
//Fixme: unused variable hero
|
||||
|
||||
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
auto resources = bankInfo->getPossibleResourcesReward();
|
||||
TResources result = TResources();
|
||||
int sum = 0;
|
||||
|
||||
for(auto & reward : resources)
|
||||
{
|
||||
result += reward.data * reward.chance;
|
||||
sum += reward.chance;
|
||||
}
|
||||
|
||||
return sum > 1 ? result / sum : result;
|
||||
}
|
||||
|
||||
int32_t getResourcesGoldReward(const TResources & res)
|
||||
{
|
||||
int32_t result = 0;
|
||||
|
||||
for(EGameResID r = EGameResID(0); r < EGameResID::COUNT; r.advance(1))
|
||||
for(auto r : GameResID::ALL_RESOURCES())
|
||||
{
|
||||
if(res[r] > 0)
|
||||
{
|
||||
result += r == EGameResID::GOLD ? res[r] : res[r] * 100;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -305,22 +284,13 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
{
|
||||
case Obj::HILL_FORT:
|
||||
return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getCreatureBankArmyReward(target, hero);
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR2:
|
||||
case Obj::CREATURE_GENERATOR3:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
return getDwellingArmyValue(ai->cb.get(), target, checkGold);
|
||||
case Obj::CRYPT:
|
||||
case Obj::SHIPWRECK:
|
||||
case Obj::SHIPWRECK_SURVIVOR:
|
||||
case Obj::WARRIORS_TOMB:
|
||||
return 1000;
|
||||
case Obj::ARTIFACT:
|
||||
return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->artType);
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
return 10000;
|
||||
case Obj::HERO:
|
||||
return relations == PlayerRelations::ENEMIES
|
||||
? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
|
||||
@ -350,7 +320,7 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
{
|
||||
for(auto artID : info.reward.artifacts)
|
||||
{
|
||||
const CArtifact * art = dynamic_cast<const CArtifact *>(VLC->artifacts()->getById(artID));
|
||||
const auto * art = dynamic_cast<const CArtifact *>(VLC->artifacts()->getById(artID));
|
||||
|
||||
rewardValue += evaluateArtifactArmyValue(art);
|
||||
}
|
||||
@ -358,7 +328,7 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
|
||||
if(!info.reward.creatures.empty())
|
||||
{
|
||||
for(auto stackInfo : info.reward.creatures)
|
||||
for(const auto & stackInfo : info.reward.creatures)
|
||||
{
|
||||
rewardValue += stackInfo.getType()->getAIValue() * stackInfo.getCount();
|
||||
}
|
||||
@ -555,13 +525,6 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target, cons
|
||||
return getResourceRequirementStrength(res);
|
||||
}
|
||||
|
||||
case Obj::CREATURE_BANK:
|
||||
{
|
||||
auto resourceReward = getCreatureBankResources(target, nullptr);
|
||||
|
||||
return getResourceRequirementStrength(resourceReward);
|
||||
}
|
||||
|
||||
case Obj::TOWN:
|
||||
{
|
||||
if(ai->buildAnalyzer->getDevelopmentInfo().empty())
|
||||
@ -672,8 +635,6 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
|
||||
case Obj::PANDORAS_BOX:
|
||||
//Can contains experience, spells, or skills (only on custom maps)
|
||||
return 2.5f;
|
||||
case Obj::PYRAMID:
|
||||
return 6.0f;
|
||||
case Obj::HERO:
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
|
||||
@ -780,22 +741,6 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
auto * mine = dynamic_cast<const CGMine*>(target);
|
||||
return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
|
||||
}
|
||||
case Obj::MYSTICAL_GARDEN:
|
||||
case Obj::WINDMILL:
|
||||
return 100;
|
||||
case Obj::CAMPFIRE:
|
||||
return 800;
|
||||
case Obj::WAGON:
|
||||
return 100;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getResourcesGoldReward(getCreatureBankResources(target, hero));
|
||||
case Obj::CRYPT:
|
||||
case Obj::DERELICT_SHIP:
|
||||
return 3000;
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
return 10000;
|
||||
case Obj::SEA_CHEST:
|
||||
return 1500;
|
||||
case Obj::PANDORAS_BOX:
|
||||
return 2500;
|
||||
case Obj::PRISON:
|
||||
|
@ -68,7 +68,12 @@ void RecruitHero::accept(AIGateway * ai)
|
||||
throw cannotFulfillGoalException("Town " + t->nodeName() + " is occupied. Cannot recruit hero!");
|
||||
|
||||
cb->recruitHero(t, heroToHire);
|
||||
|
||||
{
|
||||
std::unique_lock lockGuard(ai->nullkiller->aiStateMutex);
|
||||
|
||||
ai->nullkiller->heroManager->update();
|
||||
}
|
||||
|
||||
if(t->visitingHero)
|
||||
ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get());
|
||||
|
@ -44,7 +44,7 @@ namespace AIPathfinding
|
||||
Nullkiller * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage,
|
||||
bool allowBypassObjects)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
|
||||
:PathfinderConfig(nodeStorage, cb, makeRuleset(cb, ai, nodeStorage, allowBypassObjects)), aiNodeStorage(nodeStorage)
|
||||
{
|
||||
options.canUseCast = true;
|
||||
options.allowLayerTransitioningAfterBattle = true;
|
||||
|
@ -321,7 +321,7 @@ void ObjectGraphCalculator::addObjectActor(const CGObjectInstance * obj)
|
||||
|
||||
void ObjectGraphCalculator::addJunctionActor(const int3 & visitablePos, bool isVirtualBoat)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(syncLock);
|
||||
std::lock_guard lock(syncLock);
|
||||
|
||||
auto internalCb = temporaryActorHeroes.front()->cb;
|
||||
auto objectActor = temporaryActorHeroes.emplace_back(std::make_unique<CGHeroInstance>(internalCb)).get();
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "../../lib/CRandomGenerator.h"
|
||||
|
||||
CStupidAI::CStupidAI()
|
||||
: side(-1)
|
||||
: side(BattleSide::NONE)
|
||||
, wasWaitingForRealize(false)
|
||||
, wasUnlockingGs(false)
|
||||
{
|
||||
@ -262,7 +262,7 @@ void CStupidAI::battleStacksEffectsSet(const BattleID & battleID, const SetStack
|
||||
print("battleStacksEffectsSet called");
|
||||
}
|
||||
|
||||
void CStupidAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
|
||||
void CStupidAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide Side, bool replayAllowed)
|
||||
{
|
||||
print("battleStart called");
|
||||
side = Side;
|
||||
|
@ -17,7 +17,7 @@ class EnemyInfo;
|
||||
|
||||
class CStupidAI : public CBattleGameInterface
|
||||
{
|
||||
int side;
|
||||
BattleSide side;
|
||||
std::shared_ptr<CBattleCallback> cb;
|
||||
std::shared_ptr<Environment> env;
|
||||
|
||||
@ -47,7 +47,7 @@ public:
|
||||
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, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
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:
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/CBank.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjects/CQuest.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
|
@ -143,9 +143,9 @@ static const std::vector<BuildingID> basicGoldSource = { BuildingID::TOWN_HALL,
|
||||
static const std::vector<BuildingID> defence = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE };
|
||||
static const std::vector<BuildingID> capitolAndRequirements = { BuildingID::FORT, BuildingID::CITADEL, BuildingID::CASTLE, BuildingID::CAPITOL };
|
||||
static const std::vector<BuildingID> unitsSource = { BuildingID::DWELL_LVL_1, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3,
|
||||
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7 };
|
||||
BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7, BuildingID::DWELL_LVL_8 };
|
||||
static const std::vector<BuildingID> unitsUpgrade = { BuildingID::DWELL_LVL_1_UP, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP,
|
||||
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP };
|
||||
BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP, BuildingID::DWELL_LVL_8_UP };
|
||||
static const std::vector<BuildingID> unitGrowth = { BuildingID::HORDE_1, BuildingID::HORDE_1_UPGR, BuildingID::HORDE_2, BuildingID::HORDE_2_UPGR };
|
||||
static const std::vector<BuildingID> _spells = { BuildingID::MAGES_GUILD_1, BuildingID::MAGES_GUILD_2, BuildingID::MAGES_GUILD_3,
|
||||
BuildingID::MAGES_GUILD_4, BuildingID::MAGES_GUILD_5 };
|
||||
@ -196,7 +196,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
|
||||
return true;
|
||||
|
||||
//workaround for mantis #2696 - build capitol with separate algorithm if it is available
|
||||
if(vstd::contains(t->builtBuildings, BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
|
||||
if(t->hasBuilt(BuildingID::CITY_HALL) && getMaxPossibleGoldBuilding(t) == BuildingID::CAPITOL)
|
||||
{
|
||||
if(tryBuildNextStructure(t, capitolAndRequirements))
|
||||
return true;
|
||||
|
@ -219,12 +219,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c
|
||||
enemyFlyers->setValue(enemyStructure.flyers);
|
||||
enemySpeed->setValue(enemyStructure.maxSpeed);
|
||||
|
||||
bool bank = dynamic_cast<const CBank *>(enemy);
|
||||
if(bank)
|
||||
bankPresent->setValue(1);
|
||||
else
|
||||
bankPresent->setValue(0);
|
||||
|
||||
const CGTownInstance * fort = dynamic_cast<const CGTownInstance *>(enemy);
|
||||
if(fort)
|
||||
castleWalls->setValue(fort->fortLevel());
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
#include "../../lib/mapObjects/CBank.h"
|
||||
#include "../../lib/mapObjects/CGCreature.h"
|
||||
#include "../../lib/mapObjects/CGDwelling.h"
|
||||
#include "../../lib/gameState/InfoAboutArmy.h"
|
||||
@ -62,25 +61,6 @@ Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
||||
return result;
|
||||
}
|
||||
|
||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
|
||||
auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
|
||||
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
|
||||
ui64 totalStrength = 0;
|
||||
ui8 totalChance = 0;
|
||||
for(auto config : bankInfo->getPossibleGuards(bank->cb))
|
||||
{
|
||||
totalStrength += config.second.totalStrength * config.first;
|
||||
totalChance += config.first;
|
||||
}
|
||||
return totalStrength / std::max<ui8>(totalChance, 1); //avoid division by zero
|
||||
|
||||
}
|
||||
|
||||
float FuzzyHelper::evaluate(Goals::VisitTile & g)
|
||||
{
|
||||
if(g.parent)
|
||||
@ -301,32 +281,13 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
|
||||
cb->getTownInfo(obj, iat);
|
||||
return iat.army.getStrength();
|
||||
}
|
||||
case Obj::MONSTER:
|
||||
{
|
||||
//TODO!!!!!!!!
|
||||
const CGCreature * cre = dynamic_cast<const CGCreature *>(obj);
|
||||
return cre->getArmyStrength();
|
||||
}
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
{
|
||||
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
|
||||
return d->getArmyStrength();
|
||||
}
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
default:
|
||||
{
|
||||
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
||||
if (a)
|
||||
return a->getArmyStrength();
|
||||
}
|
||||
case Obj::CRYPT: //crypt
|
||||
case Obj::CREATURE_BANK: //crebank
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
case Obj::SHIPWRECK: //shipwreck
|
||||
case Obj::DERELICT_SHIP: //derelict ship
|
||||
case Obj::PYRAMID:
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
default:
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,6 @@
|
||||
#pragma once
|
||||
#include "FuzzyEngines.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CBank;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class DLL_EXPORT FuzzyHelper
|
||||
{
|
||||
public:
|
||||
@ -42,8 +36,6 @@ public:
|
||||
float evaluate(Goals::AbstractGoal & g);
|
||||
void setPriority(Goals::TSubgoal & g);
|
||||
|
||||
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
|
||||
|
||||
Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
|
||||
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
|
||||
|
||||
|
@ -109,7 +109,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
|
||||
if(upgradeNumber < 0)
|
||||
continue;
|
||||
|
||||
BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * GameConstants::CREATURES_PER_TOWN);
|
||||
BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->town->creatures.size());
|
||||
if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->getFullRecruitCost())) //this assumes only creatures with dwellings are assigned to faction
|
||||
{
|
||||
solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid)));
|
||||
|
@ -39,7 +39,7 @@ namespace AIPathfinding
|
||||
CPlayerSpecificInfoCallback * cb,
|
||||
VCAI * ai,
|
||||
std::shared_ptr<AINodeStorage> nodeStorage)
|
||||
:PathfinderConfig(nodeStorage, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
|
||||
:PathfinderConfig(nodeStorage, cb, makeRuleset(cb, ai, nodeStorage)), hero(nodeStorage->getHero())
|
||||
{
|
||||
options.ignoreGuards = false;
|
||||
options.useEmbarkAndDisembark = true;
|
||||
|
@ -21,7 +21,7 @@
|
||||
#include "../../lib/mapObjects/ObjectTemplate.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/bonuses/Limiters.h"
|
||||
#include "../../lib/bonuses/Updaters.h"
|
||||
@ -31,8 +31,6 @@
|
||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||
#include "../../lib/networkPacks/PacksForServer.h"
|
||||
#include "../../lib/serializer/CTypeList.h"
|
||||
#include "../../lib/serializer/BinarySerializer.h"
|
||||
#include "../../lib/serializer/BinaryDeserializer.h"
|
||||
|
||||
#include "AIhelper.h"
|
||||
|
||||
@ -1319,7 +1317,7 @@ bool VCAI::canRecruitAnyHero(const CGTownInstance * t) const
|
||||
return false;
|
||||
if(cb->getHeroesInfo().size() >= ALLOWED_ROAMING_HEROES)
|
||||
return false;
|
||||
if(cb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
|
||||
if(cb->getHeroesInfo().size() >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
|
||||
return false;
|
||||
if(!cb->getAvailableHeroes(t).size())
|
||||
return false;
|
||||
@ -1566,7 +1564,7 @@ void VCAI::completeGoal(Goals::TSubgoal goal)
|
||||
|
||||
}
|
||||
|
||||
void VCAI::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
|
||||
void VCAI::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, BattleSide side, bool replayAllowed)
|
||||
{
|
||||
NET_EVENT_HANDLER;
|
||||
assert(!playerID.isValidPlayer() || status.getBattle() == UPCOMING_BATTLE);
|
||||
@ -2130,7 +2128,7 @@ void VCAI::tryRealize(Goals::Trade & g) //trade
|
||||
//TODO trade only as much as needed
|
||||
if (toGive) //don't try to sell 0 resources
|
||||
{
|
||||
cb->trade(m, EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
|
||||
cb->trade(m->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, res, GameResID(g.resID), toGive);
|
||||
acquiredResources = static_cast<int>(toGet * (it->resVal / toGive));
|
||||
logAi->debug("Traded %d of %s for %d of %s at %s", toGive, res, acquiredResources, g.resID, obj->getObjectName());
|
||||
}
|
||||
@ -2752,8 +2750,6 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
|
||||
|
||||
if(dynamic_cast<const CGDwelling *>(obj))
|
||||
return true;
|
||||
if(dynamic_cast<const CBank *>(obj)) //banks tend to respawn often in mods
|
||||
return true;
|
||||
|
||||
switch(obj->ID)
|
||||
{
|
||||
@ -2854,12 +2850,12 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
|
||||
case Obj::MAGIC_WELL:
|
||||
return h->mana < h->manaLimit();
|
||||
case Obj::PRISON:
|
||||
return ai->myCb->getHeroesInfo().size() < VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
|
||||
return ai->myCb->getHeroesInfo().size() < cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP);
|
||||
case Obj::TAVERN:
|
||||
{
|
||||
//TODO: make AI actually recruit heroes
|
||||
//TODO: only on request
|
||||
if(ai->myCb->getHeroesInfo().size() >= VLC->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
|
||||
if(ai->myCb->getHeroesInfo().size() >= cb->getSettings().getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
|
||||
return false;
|
||||
else if(ai->ah->freeGold() < GameConstants::HERO_GOLD_COST)
|
||||
return false;
|
||||
|
@ -187,7 +187,7 @@ public:
|
||||
void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) override;
|
||||
void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
|
||||
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) 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;
|
||||
void battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
|
||||
std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
|
||||
|
||||
|
@ -217,6 +217,16 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CCallback::visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)
|
||||
{
|
||||
if(town->tempOwner!=player)
|
||||
return false;
|
||||
|
||||
VisitTownBuilding pack(town->id, buildingID);
|
||||
sendRequest(&pack);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CBattleCallback::battleMakeSpellAction(const BattleID & battleID, const BattleAction & action)
|
||||
{
|
||||
assert(action.actionType == EActionType::HERO_SPELL);
|
||||
@ -256,15 +266,15 @@ void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
|
||||
sendRequest(&pack);
|
||||
}
|
||||
|
||||
void CCallback::trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
|
||||
void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)
|
||||
{
|
||||
trade(market, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero);
|
||||
trade(marketId, mode, std::vector(1, id1), std::vector(1, id2), std::vector(1, val1), hero);
|
||||
}
|
||||
|
||||
void CCallback::trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
|
||||
void CCallback::trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)
|
||||
{
|
||||
TradeOnMarketplace pack;
|
||||
pack.marketId = dynamic_cast<const CGObjectInstance *>(market)->id;
|
||||
pack.marketId = marketId;
|
||||
pack.heroId = hero ? hero->id : ObjectInstanceID();
|
||||
pack.mode = mode;
|
||||
pack.r1 = id1;
|
||||
|
11
CCallback.h
11
CCallback.h
@ -34,7 +34,6 @@ class IBattleEventsReceiver;
|
||||
class IGameEventsReceiver;
|
||||
struct ArtifactLocation;
|
||||
class BattleStateInfoForRetreat;
|
||||
class IMarket;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
@ -76,12 +75,13 @@ public:
|
||||
//town
|
||||
virtual void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE)=0;
|
||||
virtual bool buildBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
|
||||
virtual bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID)=0;
|
||||
virtual void recruitCreatures(const CGDwelling *obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1)=0;
|
||||
virtual bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE)=0; //if newID==-1 then best possible upgrade will be made
|
||||
virtual void swapGarrisonHero(const CGTownInstance *town)=0;
|
||||
|
||||
virtual void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
|
||||
virtual void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr)=0;
|
||||
virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero)=0; //mode==0: sell val1 units of id1 resource for id2 resiurce
|
||||
virtual void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero)=0;
|
||||
|
||||
virtual int selectionMade(int selection, QueryID queryID) =0;
|
||||
virtual int sendQueryReply(std::optional<int32_t> reply, QueryID queryID) =0;
|
||||
@ -182,14 +182,15 @@ public:
|
||||
void manageHeroCostume(ObjectInstanceID hero, size_t costumeIdx, bool saveCostume) override;
|
||||
void eraseArtifactByClient(const ArtifactLocation & al) override;
|
||||
bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;
|
||||
bool visitTownBuilding(const CGTownInstance *town, BuildingID buildingID) override;
|
||||
void recruitCreatures(const CGDwelling * obj, const CArmedInstance * dst, CreatureID ID, ui32 amount, si32 level=-1) override;
|
||||
bool dismissCreature(const CArmedInstance *obj, SlotID stackPos) override;
|
||||
bool upgradeCreature(const CArmedInstance *obj, SlotID stackPos, CreatureID newID=CreatureID::NONE) override;
|
||||
void endTurn() override;
|
||||
void swapGarrisonHero(const CGTownInstance *town) override;
|
||||
void buyArtifact(const CGHeroInstance *hero, ArtifactID aid) override;
|
||||
void trade(const IMarket * market, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
|
||||
void trade(const IMarket * market, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
|
||||
void trade(const ObjectInstanceID marketId, EMarketMode mode, TradeItemSell id1, TradeItemBuy id2, ui32 val1, const CGHeroInstance * hero = nullptr) override;
|
||||
void trade(const ObjectInstanceID marketId, EMarketMode mode, const std::vector<TradeItemSell> & id1, const std::vector<TradeItemBuy> & id2, const std::vector<ui32> & val1, const CGHeroInstance * hero = nullptr) override;
|
||||
void setFormation(const CGHeroInstance * hero, EArmyFormation mode) override;
|
||||
void recruitHero(const CGObjectInstance *townOrTavern, const CGHeroInstance *hero, const HeroTypeID & nextHero=HeroTypeID::NONE) override;
|
||||
void save(const std::string &fname) override;
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=armeabi-v7a
|
||||
DEPS_FILENAME=dependencies-android-32
|
||||
. CI/android/before_install.sh
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=aarch64-v8a
|
||||
DEPS_FILENAME=dependencies-android-64
|
||||
. CI/android/before_install.sh
|
||||
|
@ -4,6 +4,4 @@ echo "ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/25.2.9519653" >> $GITHUB_ENV
|
||||
|
||||
brew install ninja
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-dependencies/releases/download/android-1.1/$DEPS_FILENAME.txz" \
|
||||
| tar -xf - --xz
|
||||
. CI/install_conan_dependencies.sh "$DEPS_FILENAME"
|
9
CI/install_conan_dependencies.sh
Normal file
9
CI/install_conan_dependencies.sh
Normal file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
RELEASE_TAG="1.2"
|
||||
FILENAME="$1"
|
||||
DOWNLOAD_URL="https://github.com/vcmi/vcmi-dependencies/releases/download/$RELEASE_TAG/$FILENAME.txz"
|
||||
|
||||
mkdir ~/.conan
|
||||
cd ~/.conan
|
||||
curl -L "$DOWNLOAD_URL" | tar -xf - --xz
|
@ -2,6 +2,4 @@
|
||||
|
||||
echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.2.1/ios-arm64.txz' \
|
||||
| tar -xf -
|
||||
. CI/install_conan_dependencies.sh "dependencies-ios"
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=intel-cross-arm
|
||||
DEPS_FILENAME=dependencies-mac-arm
|
||||
. CI/mac/before_install.sh
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DEPS_FILENAME=intel
|
||||
DEPS_FILENAME=dependencies-mac-intel
|
||||
. CI/mac/before_install.sh
|
||||
|
@ -4,6 +4,4 @@ echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
|
||||
|
||||
brew install ninja
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.2.1/$DEPS_FILENAME.txz" \
|
||||
| tar -xf -
|
||||
. CI/install_conan_dependencies.sh "$DEPS_FILENAME"
|
||||
|
@ -11,6 +11,4 @@ curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-i686-dev_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-i686-dev_10.0.0-3_all.deb;
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.2/vcmi-deps-windows-conan-w32.tgz" \
|
||||
| tar -xzf -
|
||||
. CI/install_conan_dependencies.sh "dependencies-mingw-32"
|
||||
|
@ -11,6 +11,4 @@ curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-
|
||||
curl -O -L http://mirrors.kernel.org/ubuntu/pool/universe/m/mingw-w64/mingw-w64-x86-64-dev_10.0.0-3_all.deb \
|
||||
&& sudo dpkg -i mingw-w64-x86-64-dev_10.0.0-3_all.deb;
|
||||
|
||||
mkdir ~/.conan ; cd ~/.conan
|
||||
curl -L "https://github.com/vcmi/vcmi-deps-windows-conan/releases/download/1.2/vcmi-deps-windows-conan-w64.tgz" \
|
||||
| tar -xzf -
|
||||
. CI/install_conan_dependencies.sh "dependencies-mingw"
|
||||
|
@ -403,7 +403,10 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR NOT WIN32)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT WIN32)
|
||||
# For gcc 14+ we can use -fhardened instead
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection -fcf-protection=full")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -fstack-protector-strong -fstack-clash-protection")
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcf-protection=full")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -519,7 +522,7 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_CLIENT)
|
||||
if(NOT ENABLE_MINIMAL_LIB)
|
||||
find_package(TBB REQUIRED)
|
||||
endif()
|
||||
|
||||
@ -677,6 +680,7 @@ endif()
|
||||
|
||||
if (ENABLE_CLIENT)
|
||||
add_subdirectory(client)
|
||||
add_subdirectory(clientapp)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SERVER)
|
||||
@ -722,6 +726,10 @@ endif()
|
||||
|
||||
|
||||
if(WIN32)
|
||||
if(TBB_FOUND AND MSVC)
|
||||
install_vcpkg_imported_tgt(TBB::tbb)
|
||||
endif()
|
||||
|
||||
if(USING_CONAN)
|
||||
#Conan imports enabled
|
||||
vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
|
||||
|
86
ChangeLog.md
86
ChangeLog.md
@ -1,12 +1,28 @@
|
||||
# 1.5.5 -> 1.6.0
|
||||
# 1.5.5 -> 1.6.0 (in development)
|
||||
|
||||
# General
|
||||
### General
|
||||
* Saved game size reduced by approximately 3 times, especially for large maps or games with a large number of mods.
|
||||
* Added option to start vcmi server on randomly selected TCP port
|
||||
* Fixed potential desynchronization between server and clients on randomization of map objects if client and server run on different operating systems
|
||||
* It is now possible to generate game statistics using `!statistic` command in game chat
|
||||
|
||||
# Interface
|
||||
### Stability
|
||||
* Fixed possible crash on connecting bluetooth mouse during gameplay on Android
|
||||
* VCMI will now write more detailed information to log file on crash due to uncaught exception
|
||||
|
||||
### Multiplayer
|
||||
* Implemented handicap system, with options to reduce income and growth in addition to starting resources restriction
|
||||
|
||||
### Mechanics
|
||||
* Arrow tower will now prefer to attack more units that are viewed most dangerous instead of simply attacking top-most unit
|
||||
* Score in campaigns will now be correctly tracked for games loaded from a save
|
||||
* Fixed incorrect direction of Dragon Breath attack in some cases if wide creature attacks another wide creature
|
||||
* Map events and town events are now triggered on start of turn of player affected by event, in line with H3 instead of triggering on start of new day for all players
|
||||
|
||||
### Interface
|
||||
* Implemented spell quick selection panel in combat
|
||||
* Implemented adventure map overlay accessible via Alt key that highlights all interactive objects on screen
|
||||
* Added hotkeys to reorder list of owned towns or heroes
|
||||
* The number of units resurrected using the Life Drain ability is now written to the combat log.
|
||||
* Fixed playback of audio stream with different formats from video files in some Heroes 3 versions
|
||||
* Video playback will not be replaced by a black square when another dialogue box is on top of the video.
|
||||
@ -19,17 +35,81 @@
|
||||
* Semi-transparent shadows now correctly update their transparency during fading effects, such as resource pickups
|
||||
* Game will now save all names for human player in hotseat mode
|
||||
* Added unassigned by default shortcuts for toggling visibility of visitable and blocked tiles
|
||||
* Spellbook button in battle is now blocked if hero has no spellbook
|
||||
* Adventure map will no longer scroll if window is not in focus
|
||||
|
||||
### Random Maps Generator
|
||||
* Implemented connection option 'forcePortal'
|
||||
* It is now possible to connect zone to itself using pair of portals
|
||||
|
||||
### AI
|
||||
* Fixed bug where BattleAI attempts to move double-wide unit to an unreachable hex
|
||||
* Fixed several cases where Nullkiller AI can count same dangerous object twice, doubling expected army loss.
|
||||
* Nullkiller is now capable of visiting configurable objects from mods
|
||||
* Nullkiller now uses whirlpools for map movement
|
||||
* AI can now correctly estimate effect of Dragon Breath and other similar abilities
|
||||
|
||||
### Map Editor
|
||||
* Implemented tracking of building requirements for Building Dialog
|
||||
* Added build/demolish/enable/disable all buildings options to Building Dialog in town properties
|
||||
* It is now possible to set spells allowed or required to be present in Mages Guild
|
||||
* It is now possible to add timed events to a town
|
||||
* Fixed editor not marking mod as dependency if spells from mod are used in town Mages Guild or in hero starting spells
|
||||
|
||||
### Modding
|
||||
* Fixed multiple issues with configurable town buildings
|
||||
* Added documentation for configurable town buildings. See docs/Moddders/Entities_Format/Town_Buildings_Format.md
|
||||
* Replaced some of hardcoded town buildings with configurable buildings. These building types are now deprecated and will be removed in future.
|
||||
* Added support for custom music and opening sound for a battlefield
|
||||
* Added support for multiple music tracks for towns
|
||||
* Added support for multiple music tracks for terrains on adventure map
|
||||
* Fixed several cases where vcmi will report errors in json without specifying filename of invalid file
|
||||
* It is now possible to use .zip archive for VCMI campaigns instead of raw gzip stream
|
||||
* Added support for custom region definitions (such as background images) for VCMI campaigns
|
||||
* It is now possible to change minimal values of primary skills for heroes
|
||||
* Added support for HotA bank building from Factory
|
||||
* Added support for HotA-style 8th creature in town
|
||||
|
||||
# 1.5.6 -> 1.5.7
|
||||
|
||||
* Fixed game freeze if player is attacked in online multiplayer game by another player when he has unread dialogs, such as new week notification
|
||||
* Fixed possible game crash after being attacked by enemy with artifact that blocks spellcasting
|
||||
* Fixed heroes on map limit game setting not respected when moving hero from town garrison.
|
||||
* Add workaround to fix possible crash on attempt to start previously generated random map that has players without owned heroes or towns
|
||||
* Fixed crash on right-clicking spell icon when receiving unlearnable spells from Pandora
|
||||
* Fixed possible text overflow in match information box in online lobby
|
||||
* Fixed overlapping text in lobby login window
|
||||
* Fixed excessive removal of open dialogs such as new week or map events on new turn
|
||||
* Fixed objects like Mystical Gardens not resetting their state on new week correctly
|
||||
|
||||
# 1.5.5 -> 1.5.6
|
||||
|
||||
### Stability
|
||||
* Fixed possible crash on transferring hero to next campaign scenario if hero has combined artifact some components of which can be transferred
|
||||
* Fixed possible crash on transferring hero to next campaign scenario that has creature with faction limiter in his army
|
||||
* Fixed possible crash on application shutdown due to incorrect destruction order of UI entities
|
||||
|
||||
### Multiplayer
|
||||
* Mod compatibility issues when joining a lobby room now use color coding to make them less easy to miss.
|
||||
* Incompatible mods are now placed before compatible mods when joining lobby room.
|
||||
* Fixed text overflow in online lobby interface
|
||||
* Fixed jittering simultaneous turns slider after moving it twice over short period
|
||||
* Fixed non-functioning slider in invite to game room dialog
|
||||
|
||||
### Interface
|
||||
* Fixed some shortcuts that were not active during the enemy's turn, such as Thieves' Guild.
|
||||
* Game now correctly uses melee damage calculation when forcing a melee attack with a shooter.
|
||||
* Game will now close all open dialogs on start of our turn, to avoid bugs like locked right-click popups
|
||||
|
||||
### Map Objects
|
||||
* Spells the hero can't learn are no longer hidden when received from a rewardable object, such as the Pandora Box
|
||||
* Spells that cannot be learned are now displayed with gray text in the name of the spell.
|
||||
* Configurable objects with scouted state such as Witch Hut in HotA now correctly show their reward on right click after vising them but refusing to accept reward
|
||||
* Right-click tooltip on map dwelling now always shows produced creatures. Player that owns the dwelling can also see number of creatures available for recruit
|
||||
|
||||
### Modding
|
||||
* Fixed possible crash on invalid SPELL_LIKE_ATTACK bonus
|
||||
* Added compatibility check when loading maps with old names for boats
|
||||
|
||||
# 1.5.4 -> 1.5.5
|
||||
|
||||
|
33
Global.h
33
Global.h
@ -102,6 +102,12 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
|
||||
# define STRONG_INLINE inline
|
||||
#endif
|
||||
|
||||
// Required for building boost::stacktrace on macOS.
|
||||
// See https://github.com/boostorg/stacktrace/issues/88
|
||||
#if defined(VCMI_APPLE)
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#include <algorithm>
|
||||
@ -700,6 +706,33 @@ namespace vstd
|
||||
return a + (b - a) * f;
|
||||
}
|
||||
|
||||
/// Divides dividend by divisor and rounds result up
|
||||
/// For use with integer-only arithmetic
|
||||
template<typename Integer1, typename Integer2>
|
||||
Integer1 divideAndCeil(const Integer1 & dividend, const Integer2 & divisor)
|
||||
{
|
||||
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
|
||||
return (dividend + divisor - 1) / divisor;
|
||||
}
|
||||
|
||||
/// Divides dividend by divisor and rounds result to nearest
|
||||
/// For use with integer-only arithmetic
|
||||
template<typename Integer1, typename Integer2>
|
||||
Integer1 divideAndRound(const Integer1 & dividend, const Integer2 & divisor)
|
||||
{
|
||||
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
|
||||
return (dividend + divisor / 2 - 1) / divisor;
|
||||
}
|
||||
|
||||
/// Divides dividend by divisor and rounds result down
|
||||
/// For use with integer-only arithmetic
|
||||
template<typename Integer1, typename Integer2>
|
||||
Integer1 divideAndFloor(const Integer1 & dividend, const Integer2 & divisor)
|
||||
{
|
||||
static_assert(std::is_integral_v<Integer1> && std::is_integral_v<Integer2>, "This function should only be used with integral types");
|
||||
return dividend / divisor;
|
||||
}
|
||||
|
||||
template<typename Floating>
|
||||
bool isAlmostZero(const Floating & value)
|
||||
{
|
||||
|
@ -72,6 +72,11 @@
|
||||
"vcmi.lobby.noUnderground" : "无地下部分",
|
||||
"vcmi.lobby.sortDate" : "以修改时间排序地图",
|
||||
"vcmi.lobby.backToLobby" : "返回大厅",
|
||||
"vcmi.lobby.author" : "作者",
|
||||
"vcmi.lobby.handicap" : "障碍",
|
||||
"vcmi.lobby.handicap.resource" : "给予玩家起始资源以外的更多资源,允许负值,但总量不会低于0(玩家永远不会能以负资源开始游戏)。",
|
||||
"vcmi.lobby.handicap.income" : "按百分比改变玩家的各种收入,向上取整。",
|
||||
"vcmi.lobby.handicap.growth" : "改变玩家拥有的城镇的生物增长率,向上取整。",
|
||||
|
||||
"vcmi.lobby.login.title" : "VCMI大厅",
|
||||
"vcmi.lobby.login.username" : "用户名:",
|
||||
@ -157,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "其他设置", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "城镇画面",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "统计",
|
||||
"vcmi.statisticWindow.tsvCopy" : "复制到剪切板",
|
||||
"vcmi.statisticWindow.selectView" : "选择视角",
|
||||
"vcmi.statisticWindow.value" : "值",
|
||||
"vcmi.statisticWindow.title.overview" : "概况",
|
||||
"vcmi.statisticWindow.title.resources" : "资源",
|
||||
"vcmi.statisticWindow.title.income" : "收入",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "英雄数量",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "城镇数量",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "宝物数量",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "野外巢穴数量",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "矿井数量",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "部队强度",
|
||||
"vcmi.statisticWindow.title.experience" : "经验",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "部队花费",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "建造花费",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "地图探索比例",
|
||||
"vcmi.statisticWindow.param.playerName" : "玩家名称",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "存活天数",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "最大英雄等级",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "胜率(对英雄)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "胜率(对中立生物)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "战斗(对英雄)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "战斗(对中立生物)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "最大部队强度",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "交易量",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "方尖塔访问",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "占领城镇",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "击败对手最强英雄",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "找到神器",
|
||||
"vcmi.statisticWindow.icon.defeated" : "被击败",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "全屏 (无边框)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{全屏}\n\n选中时,VCMI将以无边框全屏模式运行。该模式下,游戏会始终和桌面分辨率保持一致,无视设置的分辨率。 ",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "全屏 (独占)",
|
||||
@ -237,6 +274,8 @@
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.help": "{跳过战斗开始音乐}\n\n战斗开始音乐播放期间,你也能够进行操作。",
|
||||
"vcmi.battleOptions.endWithAutocombat.hover": "结束战斗",
|
||||
"vcmi.battleOptions.endWithAutocombat.help": "{结束战斗}\n\n以自动战斗立即结束剩余战斗过程",
|
||||
"vcmi.battleOptions.showQuickSpell.hover": "展示快捷法术面板",
|
||||
"vcmi.battleOptions.showQuickSpell.help": "{展示快捷法术面板}\n\n展示快捷选择法术的面板。",
|
||||
|
||||
"vcmi.adventureMap.revisitObject.hover" : "重新访问",
|
||||
"vcmi.adventureMap.revisitObject.help" : "{重新访问}\n\n让当前英雄重新访问地图建筑或城镇。",
|
||||
@ -285,17 +324,9 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "必须先建造基础建筑 %s",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "没有可供招募的生物。",
|
||||
"vcmi.townHall.greetingManaVortex" : "接近%s时,你会全身充满活力,并且你的魔法值会加倍。",
|
||||
"vcmi.townHall.greetingKnowledge" : "你研究了%s的浮雕,洞察了魔法的秘密(知识+1)。",
|
||||
"vcmi.townHall.greetingSpellPower" : "%s教你如何运用魔法力量(力量+1)。",
|
||||
"vcmi.townHall.greetingExperience" : "参观%s可以让你学会许多新的技能(经验值+1000)。",
|
||||
"vcmi.townHall.greetingAttack" : "在%s中稍待片刻可以让你学会更有效的战斗技巧(攻击力+1)。",
|
||||
"vcmi.townHall.greetingDefence" : "在%s中稍待片刻,富有战斗经验的战士会教你防御技巧(防御力+1)。",
|
||||
"vcmi.townHall.hasNotProduced" : "本周%s并没有产生什么资源。",
|
||||
"vcmi.townHall.hasProduced" : "本周%s产生了%d个%s。",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s 给予英雄 +%d %s%s。",
|
||||
"vcmi.townHall.greetingCustomUntil" : "直到下一场战斗。",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s使你的魔法值恢复到最大值。",
|
||||
|
||||
"vcmi.townStructure.bank.borrow" : "你走进银行。一位银行职员看到你,说道:“我们为您提供了一个特别优惠。您可以向我们借2500金币,期限为5天。您每天需要偿还500金币。”",
|
||||
"vcmi.townStructure.bank.payBack" : "你走进银行。一位银行职员看到你,说道:“您已经获得了贷款。还清之前,不能再申请新的贷款。”",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "以下任一前提:",
|
||||
"vcmi.logicalExpressions.allOf" : "以下所有前提:",
|
||||
@ -629,5 +660,7 @@
|
||||
"core.bonus.WATER_IMMUNITY.name": "水系免疫",
|
||||
"core.bonus.WATER_IMMUNITY.description": "免疫水系魔法",
|
||||
"core.bonus.WIDE_BREATH.name": "弧形焰息",
|
||||
"core.bonus.WIDE_BREATH.description": "大范围喷吐攻击(目标左右以及后方共6格)"
|
||||
"core.bonus.WIDE_BREATH.description": "大范围喷吐攻击(目标左右以及后方共6格)",
|
||||
"core.bonus.DISINTEGRATE.name": "解体",
|
||||
"core.bonus.DISINTEGRATE.description": "死亡后不会留下尸体"
|
||||
}
|
||||
|
@ -278,17 +278,6 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Základní budova %s musí být postavena jako první",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Žádné jednotky k vycvičení!",
|
||||
"vcmi.townHall.greetingManaVortex" : "Při pobytu u místa %s se vaše tělo naplnilo novou energií. Máte dvojnásobné množství maximální magické energie.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Studujete glyfy na the %s a porozumíte fungování různých magií (+1 Znalosti).",
|
||||
"vcmi.townHall.greetingSpellPower" : "%s vás učí nové cesty zaměření vaší magické síly (+1 Síla kouzel).",
|
||||
"vcmi.townHall.greetingExperience" : "Návštěva %s vás naučila spoustu nových dovedností (+1000 zkušeností).",
|
||||
"vcmi.townHall.greetingAttack" : "Čas strávený poblíž místa zvaného %s vám dovolil se naučit efektivnější bojové dovednosti (+1 Útočná síla).",
|
||||
"vcmi.townHall.greetingDefence" : "Trávíte čas na místě zvaném %s, zkušení bojovníci vás u toho naučili nové metody obrany (+1 Obranná síla).",
|
||||
"vcmi.townHall.hasNotProduced" : "%s - zatím nic nevyrobeno.",
|
||||
"vcmi.townHall.hasProduced" : "%s - vyrobeno %d %s tento týden.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s vám dává +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " do další bitvy.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s - obnoveno na maximum vaši magickou energii.",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Něco z následujících:",
|
||||
"vcmi.logicalExpressions.allOf" : "Všechny následující:",
|
||||
|
@ -73,6 +73,10 @@
|
||||
"vcmi.lobby.sortDate" : "Sorts maps by change date",
|
||||
"vcmi.lobby.backToLobby" : "Return to lobby",
|
||||
"vcmi.lobby.author" : "Author",
|
||||
"vcmi.lobby.handicap" : "Handicap",
|
||||
"vcmi.lobby.handicap.resource" : "Gives players appropriate resources to start with in addition to the normal starting resources. Negative values are allowed, but are limited to 0 in total (the player never starts with negative resources).",
|
||||
"vcmi.lobby.handicap.income" : "Changes the player's various incomes by the percentage. Is rounded up.",
|
||||
"vcmi.lobby.handicap.growth" : "Changes the growth rate of creatures in the towns owned by the player. Is rounded up.",
|
||||
|
||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||
"vcmi.lobby.login.username" : "Username:",
|
||||
@ -158,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "Other Settings", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "Town Screen",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statistics",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Data to clipboard",
|
||||
"vcmi.statisticWindow.selectView" : "Select view",
|
||||
"vcmi.statisticWindow.value" : "Value",
|
||||
"vcmi.statisticWindow.title.overview" : "Overview",
|
||||
"vcmi.statisticWindow.title.resources" : "Resources",
|
||||
"vcmi.statisticWindow.title.income" : "Income",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "No. of heroes",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "No. of towns",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "No. of artifacts",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "No. of dwellings",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "No. of mines",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Army strength",
|
||||
"vcmi.statisticWindow.title.experience" : "Experience",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Army costs",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Building costs",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Map explore ratio",
|
||||
"vcmi.statisticWindow.param.playerName" : "Player name",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Days survived",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Max hero level",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Win ratio (vs. hero)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Win ratio (vs. neutral)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Battles (vs. hero)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Battles (vs. neutral)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Max total army strength",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Trade volume",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Obelisk visited",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Town captured",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Strongest hero of opponent defeated",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Grail found",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Defeated",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Fullscreen (borderless)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Borderless Fullscreen}\n\nIf selected, VCMI will run in borderless fullscreen mode. In this mode, game will always use same resolution as desktop, ignoring selected resolution.",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Fullscreen (exclusive)",
|
||||
@ -288,17 +324,9 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Base building %s must be built first",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "There are no creatures to recruit!",
|
||||
"vcmi.townHall.greetingManaVortex" : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.",
|
||||
"vcmi.townHall.greetingKnowledge" : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).",
|
||||
"vcmi.townHall.greetingSpellPower" : "The %s teaches you new ways to focus your magical powers (+1 Power).",
|
||||
"vcmi.townHall.greetingExperience" : "A visit to the %s teaches you many new skills (+1000 Experience).",
|
||||
"vcmi.townHall.greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
|
||||
"vcmi.townHall.greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
|
||||
"vcmi.townHall.hasNotProduced" : "The %s has not produced anything yet.",
|
||||
"vcmi.townHall.hasProduced" : "The %s produced %d %s this week.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s gives you +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " until next battle.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s has restored your spell points to maximum.",
|
||||
|
||||
"vcmi.townStructure.bank.borrow" : "You enter the bank. A banker sees you and says: \"We have made a special offer for you. You can take a loan of 2500 gold from us for 5 days. You will have to repay 500 gold every day.\"",
|
||||
"vcmi.townStructure.bank.payBack" : "You enter the bank. A banker sees you and says: \"You have already got your loan. Pay it back before taking a new one.\"",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Any of the following:",
|
||||
"vcmi.logicalExpressions.allOf" : "All of the following:",
|
||||
@ -632,5 +660,7 @@
|
||||
"core.bonus.WATER_IMMUNITY.name": "Water immunity",
|
||||
"core.bonus.WATER_IMMUNITY.description": "Immune to all spells from the school of Water magic",
|
||||
"core.bonus.WIDE_BREATH.name": "Wide breath",
|
||||
"core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)"
|
||||
"core.bonus.WIDE_BREATH.description": "Wide breath attack (multiple hexes)",
|
||||
"core.bonus.DISINTEGRATE.name": "Disintegrate",
|
||||
"core.bonus.DISINTEGRATE.description": "No corpse remains after death"
|
||||
}
|
||||
|
@ -135,17 +135,6 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Le bâtiment de base %s doit être construit avant",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Il n'y a aucune créature à recruter !",
|
||||
"vcmi.townHall.greetingManaVortex" : "Alors que vous approchez du %s, votre corps est rempli d'une nouvelle énergie. Vous avez doublé vos points de sort normaux.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Vous étudiez les glyphes sur le %s et découvrez le fonctionnement de diverses magies (+1 Connaissance).",
|
||||
"vcmi.townHall.greetingSpellPower" : "Le %s vous apprend de nouvelles façons de concentrer vos pouvoirs magiques (+1 Pouvoir).",
|
||||
"vcmi.townHall.greetingExperience" : "Une visite au %s vous apprend de nombreuses nouvelles compétences (+1000 Expérience).",
|
||||
"vcmi.townHall.greetingAttack" : "Un peu de temps passé au %s vous permet d'apprendre des compétences de combat plus efficaces (+1 compétence d'attaque).",
|
||||
"vcmi.townHall.greetingDefence" : "En passant du temps dans le %s, les guerriers expérimentés qui s'y trouvent vous enseignent des compétences défensives supplémentaires (+1 Défense).",
|
||||
"vcmi.townHall.hasNotProduced" : "Le %s n'a encore rien produit.",
|
||||
"vcmi.townHall.hasProduced" : "Le %s a produit %d %s cette semaine.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s vous offre +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " jusqu'à la prochaine bataille.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s a restauré vos points de sort au maximum.",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "L'un des éléments suivants :",
|
||||
"vcmi.logicalExpressions.allOf" : "Tous les éléments suivants :",
|
||||
|
@ -73,6 +73,10 @@
|
||||
"vcmi.lobby.sortDate" : "Ordnet Karten nach Änderungsdatum",
|
||||
"vcmi.lobby.backToLobby" : "Zur Lobby zurückkehren",
|
||||
"vcmi.lobby.author" : "Author",
|
||||
"vcmi.lobby.handicap" : "Handicap",
|
||||
"vcmi.lobby.handicap.resource" : "Gibt den Spielern entsprechende Ressourcen zum Start zusätzlich zu den normalen Startressourcen. Negative Werte sind erlaubt, werden aber insgesamt auf 0 begrenzt (der Spieler beginnt nie mit negativen Ressourcen).",
|
||||
"vcmi.lobby.handicap.income" : "Verändert die verschiedenen Einkommen des Spielers um den Prozentsatz. Wird aufgerundet.",
|
||||
"vcmi.lobby.handicap.growth" : "Verändert die Wachstumsrate der Kreaturen in den Städten, die der Spieler besitzt. Wird aufgerundet.",
|
||||
|
||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||
"vcmi.lobby.login.username" : "Benutzername:",
|
||||
@ -158,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "Andere Einstellungen", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statistik",
|
||||
"vcmi.statisticWindow.tsvCopy" : "In Zwischenablage",
|
||||
"vcmi.statisticWindow.selectView" : "Ansicht wählen",
|
||||
"vcmi.statisticWindow.value" : "Wert",
|
||||
"vcmi.statisticWindow.title.overview" : "Überblick",
|
||||
"vcmi.statisticWindow.title.resources" : "Ressourcen",
|
||||
"vcmi.statisticWindow.title.income" : "Einkommen",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "Nr. der Helden",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "Nr. der Städte",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "Nr. der Artefakte",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "Nr. der Behausungen",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "Nr. der Minen",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Armeestärke",
|
||||
"vcmi.statisticWindow.title.experience" : "Erfahrung",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Armeekosten",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Gebäudekosten",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Maperkundungsrate",
|
||||
"vcmi.statisticWindow.param.playerName" : "Spielername",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Tage überlebt",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Max Heldenlevel",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Sieg Verh. (Helden)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Sieg Verh. (Neutral)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Kämpfe (Helden)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Kämpfe (Neutral)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Max Gesamt-Armeestärke",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Handelsvol.",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Obelisk besucht",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Stadt erobert",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Stärksten Helden eines Gegners besiegt",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Gral gefunden",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Besiegt",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Vollbild (randlos)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Randloses Vollbild}\n\nWenn diese Option ausgewählt ist, wird VCMI im randlosen Vollbildmodus ausgeführt. In diesem Modus wird das Spiel immer dieselbe Auflösung wie der Desktop verwenden und die gewählte Auflösung ignorieren.",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Vollbild (exklusiv)",
|
||||
@ -288,17 +324,6 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Basis Gebäude %s muss als erstes gebaut werden",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Es gibt keine Kreaturen zu rekrutieren!",
|
||||
"vcmi.townHall.greetingManaVortex" : "Wenn Ihr Euch den %s nähert, wird Euer Körper mit neuer Energie gefüllt. Ihr habt Eure normalen Zauberpunkte verdoppelt.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Ihr studiert die Glyphen auf dem %s und erhaltet Einblick in die Funktionsweise verschiedener Magie (+1 Wissen).",
|
||||
"vcmi.townHall.greetingSpellPower" : "Der %s lehrt Euch neue Wege, Eure magischen Kräfte zu bündeln (+1 Kraft).",
|
||||
"vcmi.townHall.greetingExperience" : "Ein Besuch bei den %s bringt Euch viele neue Fähigkeiten bei (+1000 Erfahrung).",
|
||||
"vcmi.townHall.greetingAttack" : "Nach einiger Zeit im %s könnt Ihr effizientere Kampffertigkeiten erlernen (+1 Angriffsfertigkeit).",
|
||||
"vcmi.townHall.greetingDefence" : "Wenn Ihr Zeit im %s verbringt, bringen Euch die erfahrenen Krieger dort zusätzliche Verteidigungsfähigkeiten bei (+1 Verteidigung).",
|
||||
"vcmi.townHall.hasNotProduced" : "Die %s hat noch nichts produziert.",
|
||||
"vcmi.townHall.hasProduced" : "Die %s hat diese Woche %d %s produziert.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s gibt Ihnen +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " bis zur nächsten Schlacht.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s hat Eure Zauberpunkte wieder auf das Maximum erhöht.",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Eines der folgenden:",
|
||||
"vcmi.logicalExpressions.allOf" : "Alles der folgenden:",
|
||||
|
@ -18,8 +18,8 @@
|
||||
"vcmi.adventureMap.noTownWithTavern" : "Brak dostępnego miasta z karczmą!",
|
||||
"vcmi.adventureMap.spellUnknownProblem" : "Nieznany problem z zaklęciem, brak dodatkowych informacji.",
|
||||
"vcmi.adventureMap.playerAttacked" : "Gracz został zaatakowany: %s",
|
||||
"vcmi.adventureMap.moveCostDetails" : "Punkty ruchu - Koszt: %TURNS tury + %POINTS punkty, Pozostałe punkty: %REMAINING",
|
||||
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Punkty ruchu - Koszt: %POINTS punkty, Pozostałe punkty: %REMAINING",
|
||||
"vcmi.adventureMap.moveCostDetails" : "Punkty ruchu - Koszt: %TURNS tury + %POINTS punktów, Pozostanie: %REMAINING punktów",
|
||||
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Punkty ruchu - Koszt: %POINTS punktów, Pozostanie: %REMAINING punktów",
|
||||
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Wybacz, powtórka ruchu wroga nie została jeszcze zaimplementowana!",
|
||||
|
||||
"vcmi.capitalColors.0" : "Czerwony",
|
||||
@ -72,6 +72,11 @@
|
||||
"vcmi.lobby.noUnderground" : "brak podziemi",
|
||||
"vcmi.lobby.sortDate" : "Sortuj mapy według daty modyfikacji",
|
||||
"vcmi.lobby.backToLobby" : "Wróc do lobby",
|
||||
"vcmi.lobby.author" : "Autor",
|
||||
"vcmi.lobby.handicap" : "Balans",
|
||||
"vcmi.lobby.handicap.resource" : "Przyznaje graczom zasoby na start, oprócz normalnych zasobów początkowych. Wartości ujemne są dozwolone, ale łącznie gracz nie może zaczynać na minus.",
|
||||
"vcmi.lobby.handicap.income" : "Ustala procentowy współczynnik przychodu gracza. Zaokrąglone w górę.",
|
||||
"vcmi.lobby.handicap.growth" : "Ustala współczynnik populacji stworzeń w miastach należących do gracza. Zaokrąglone w górę.",
|
||||
|
||||
"vcmi.lobby.login.title" : "Lobby sieciowe VCMI",
|
||||
"vcmi.lobby.login.username" : "Użytkownik:",
|
||||
@ -79,7 +84,7 @@
|
||||
"vcmi.lobby.login.error" : "Błąd połączenia: %s",
|
||||
"vcmi.lobby.login.create" : "Nowe konto",
|
||||
"vcmi.lobby.login.login" : "Login",
|
||||
"vcmi.lobby.login.as" : "Zaloguj jako %s",
|
||||
"vcmi.lobby.login.as" : "Logowanie jako %s",
|
||||
"vcmi.lobby.header.rooms" : "Pokoje - %d",
|
||||
"vcmi.lobby.header.channels" : "Kanały tekstowe",
|
||||
"vcmi.lobby.header.chat.global" : "Chat globalny - %s", // %s -> language name
|
||||
@ -88,7 +93,7 @@
|
||||
"vcmi.lobby.header.history" : "Twoje poprzednie gry",
|
||||
"vcmi.lobby.header.players" : "Graczy online - %d",
|
||||
"vcmi.lobby.match.solo" : "Gra jednoosobowa",
|
||||
"vcmi.lobby.match.duel" : "Graj z %s", // %s -> nickname of another player
|
||||
"vcmi.lobby.match.duel" : "vs. %s", // %s -> nickname of another player
|
||||
"vcmi.lobby.match.multi" : "%d graczy",
|
||||
"vcmi.lobby.room.create" : "Stwórz nowy pokój",
|
||||
"vcmi.lobby.room.players.limit" : "Limit graczy",
|
||||
@ -125,20 +130,20 @@
|
||||
"vcmi.lobby.mod.state.version" : "Niepoprawna wersja",
|
||||
"vcmi.lobby.mod.state.excessive" : "Musi być wyłączony",
|
||||
"vcmi.lobby.mod.state.missing" : "Nie zainstalowany",
|
||||
"vcmi.lobby.pvp.coin.hover" : "Moneta",
|
||||
"vcmi.lobby.pvp.coin.help" : "Rzut monetą",
|
||||
"vcmi.lobby.pvp.randomTown.hover" : "Losowe miasto",
|
||||
"vcmi.lobby.pvp.randomTown.help" : "Wyświetli nazwę wylosowanego miasta na czacie",
|
||||
"vcmi.lobby.pvp.randomTownVs.hover" : "Losowe miasto vs.",
|
||||
"vcmi.lobby.pvp.randomTownVs.help" : "Wyświetli nazwę 2 wylosowanych miast na czacie",
|
||||
"vcmi.lobby.pvp.coin.hover" : "Rzut monetą",
|
||||
"vcmi.lobby.pvp.coin.help" : "Wyświetli symulację rzutu monetą na czacie, wskazując 0 lub 1 w zależności od rezultatu rzutu.",
|
||||
"vcmi.lobby.pvp.randomTown.hover" : "Wylosuj miasto",
|
||||
"vcmi.lobby.pvp.randomTown.help" : "Wyświetli nazwę wylosowanego miasta na czacie, które nie zostało zablokowane na liście",
|
||||
"vcmi.lobby.pvp.randomTownVs.hover" : "Wylosuj 2 miasta",
|
||||
"vcmi.lobby.pvp.randomTownVs.help" : "Wyświetli nazwę 2 wylosowanych miast na czacie, które nie zostały zablokowane na liście",
|
||||
"vcmi.lobby.pvp.versus" : "vs.",
|
||||
|
||||
"vcmi.client.errors.invalidMap" : "{Błędna mapa lub kampania}\n\nNie udało się stworzyć gry! Wybrana mapa lub kampania jest niepoprawna lub uszkodzona. Powód:\n%s",
|
||||
"vcmi.client.errors.missingCampaigns" : "{Brakujące pliki gry}\n\nPliki kampanii nie zostały znalezione! Możliwe że używasz niekompletnych lub uszkodzonych plików Heroes 3. Spróbuj ponownej instalacji plików gry.",
|
||||
"vcmi.server.errors.disconnected" : "{Błąd sieciowy}\n\nUtracono połączenie z serwerem!",
|
||||
"vcmi.server.errors.existingProcess" : "Inny proces 'vcmiserver' został już uruchomiony, zakończ go nim przejdziesz dalej",
|
||||
"vcmi.server.errors.modsToEnable" : "{Następujące mody są wymagane do wczytania gry}",
|
||||
"vcmi.server.errors.modsToDisable" : "{Następujące mody muszą zostać wyłączone}",
|
||||
"vcmi.server.confirmReconnect" : "Połączyć ponownie z ostatnią sesją?",
|
||||
"vcmi.server.errors.modNoDependency" : "Nie udało się wczytać moda {'%s'}!\n Jest on zależny od moda {'%s'} który nie jest aktywny!\n",
|
||||
"vcmi.server.errors.modConflict" : "Nie udało się wczytać moda {'%s'}!\n Konflikty z aktywnym modem {'%s'}!\n",
|
||||
"vcmi.server.errors.unknownEntity" : "Nie udało się wczytać zapisu! Nieznany element '%s' znaleziony w pliku zapisu! Zapis może nie być zgodny z aktualnie zainstalowaną wersją modów!",
|
||||
@ -157,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "Inne ustawienia", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "Ekran miasta",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statystyki",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Kopiuj do schowka",
|
||||
"vcmi.statisticWindow.selectView" : "Tryb widoku",
|
||||
"vcmi.statisticWindow.value" : "Wartość",
|
||||
"vcmi.statisticWindow.title.overview" : "Przegląd",
|
||||
"vcmi.statisticWindow.title.resources" : "Surowce",
|
||||
"vcmi.statisticWindow.title.income" : "Przychód",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "Lb. bohaterów",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "Lb. miast",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "Lb. artefaktów",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "Lb. siedlisk",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "Lb. kopalni",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Siła armii",
|
||||
"vcmi.statisticWindow.title.experience" : "Doświadczenie",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Koszty armii",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Koszty budowy",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Odkrycie mapy",
|
||||
"vcmi.statisticWindow.param.playerName" : "Imię",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Przeżytych dni",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Maks. poziom bohatera",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Zwycięstw (vs. bohaterom)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Zwycięstw (vs. neutralnym)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Walk (vs. bohaterom)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Walk (vs. neutralnym)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Maks. siła armii",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Wielkość handlu",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Lb. obelisków",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Miasto zdobyte",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Najsilniejszy bohater przeciwnika pokonany",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Gral znaleziony",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Pokonany",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Pełny ekran (bez ramek)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Pełny ekran w trybie okna}\n\nVCMI będzie działać w trybie okna pełnoekranowego. W tym trybie gra będzie zawsze używać rozdzielczości pulpitu, ignorując wybraną rozdzielczość.",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Pełny ekran (tradycyjny)",
|
||||
@ -237,6 +274,8 @@
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Pomiń czekanie startowe}\n\n Pomija konieczność czekania podczas muzyki startowej, która jest odtwarzana na początku każdej bitwy przed rozpoczęciem akcji.",
|
||||
"vcmi.battleOptions.endWithAutocombat.hover": "Natychmiastowe auto-walki",
|
||||
"vcmi.battleOptions.endWithAutocombat.help": "{Natychmiastowe auto-walki}\n\nAuto-walka natychmiastowo toczy walkę do samego końca",
|
||||
"vcmi.battleOptions.showQuickSpell.hover": "Szybki dostęp do magii",
|
||||
"vcmi.battleOptions.showQuickSpell.help": "{Szybki dostęp do magii}\n\nPokazuje panel szybkiego dostępu do czarów",
|
||||
|
||||
"vcmi.adventureMap.revisitObject.hover" : "Odwiedź obiekt ponownie",
|
||||
"vcmi.adventureMap.revisitObject.help" : "{Odwiedź obiekt ponownie}\n\nJeżeli bohater aktualnie stoi na polu odwiedzającym obiekt za pomocą tego przycisku może go odwiedzić ponownie.",
|
||||
@ -285,17 +324,9 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Podstawowy budynek %s musi zostać najpierw wybudowany",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Brak stworzeń do rekrutacji!",
|
||||
"vcmi.townHall.greetingManaVortex" : "Zbliżając się do %s czujesz jak twoje ciało wypełnia energia. Ilość pkt. magii, które posiadasz, zwiększa się dwukrotnie.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Studiując napisy na %s odkrywasz nowe aspekty stosowania magii (wiedza +1).",
|
||||
"vcmi.townHall.greetingSpellPower" : "Odwiedzając %s dowiadujesz się, jak zwiększyć potęgę swojej mocy magicznej (moc +1).",
|
||||
"vcmi.townHall.greetingExperience" : "Wizyta w %s zwiększa twoje doświadczenie (doświadczenie +1000).",
|
||||
"vcmi.townHall.greetingAttack" : "Krótka wizyka w %s umożliwia ci polepszenie technik walki (atak +1).",
|
||||
"vcmi.townHall.greetingDefence" : "Odwiedzasz %s. Doświadczeni żołnierze, którzy tam przebywają, uczą cię sztuki skutecznej obrony (obrona +1).",
|
||||
"vcmi.townHall.hasNotProduced" : "%s nic jeszcze nie wyprodukował.",
|
||||
"vcmi.townHall.hasProduced" : "%s wyprodukował w tym tygodniu: %d %s.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s daje tobie +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " do następnej bitwy.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s przywraca ci wszystkie punkty magii.",
|
||||
|
||||
"vcmi.townStructure.bank.borrow" : "Wchodzisz do banku. Bankier cię widzi i mówi: \"Złożyliśmy ci specjalną ofertę. Możesz wziąć od nas pożyczkę w wysokości 2500 złota na 5 dni. Będziesz musiał spłacać 500 złota każdego dnia.\"",
|
||||
"vcmi.townStructure.bank.payBack" : "Wchodzisz do banku. Bankier cię widzi i mówi: „Już dostałeś pożyczkę. Spłać ją zanim weźmiesz nową.\"",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Dowolne spośród:",
|
||||
"vcmi.logicalExpressions.allOf" : "Wszystkie spośród:",
|
||||
@ -487,7 +518,7 @@
|
||||
"core.bonus.ADDITIONAL_RETALIATION.name": "Dodatkowy odwet",
|
||||
"core.bonus.ADDITIONAL_RETALIATION.description": "${val} dodatkowy kontratak",
|
||||
"core.bonus.AIR_IMMUNITY.name": "Odporność: Powietrze",
|
||||
"core.bonus.AIR_IMMUNITY.description": "Odporny na wszystkie czary szkoły powietrza",
|
||||
"core.bonus.AIR_IMMUNITY.description": "Odporny na magię powietrza",
|
||||
"core.bonus.ATTACKS_ALL_ADJACENT.name": "Obrotowy atak",
|
||||
"core.bonus.ATTACKS_ALL_ADJACENT.description": "Atakuje wszystkich sąsiadujących wrogów",
|
||||
"core.bonus.BLOCKS_RETALIATION.name": "Bez kontrataku",
|
||||
@ -509,13 +540,13 @@
|
||||
"core.bonus.DEFENSIVE_STANCE.name": "Bonus do obrony",
|
||||
"core.bonus.DEFENSIVE_STANCE.description": "+${val} Obrony kiedy broni",
|
||||
"core.bonus.DESTRUCTION.name": "Destrukcja",
|
||||
"core.bonus.DESTRUCTION.description": "Ma ${val}% szans na zabicie dodatkowych jednostek po ataku",
|
||||
"core.bonus.DESTRUCTION.description": "${val}% szans na zabicie dodatkowych jednostek po ataku",
|
||||
"core.bonus.DOUBLE_DAMAGE_CHANCE.name": "Uderzenie Śmierci",
|
||||
"core.bonus.DOUBLE_DAMAGE_CHANCE.description": "${val}% szans na podwójne obrażenia",
|
||||
"core.bonus.DRAGON_NATURE.name": "Smok",
|
||||
"core.bonus.DRAGON_NATURE.description": "Stworzenie posiada smoczą naturę",
|
||||
"core.bonus.EARTH_IMMUNITY.name": "Odporność: Ziemia",
|
||||
"core.bonus.EARTH_IMMUNITY.description": "Odporny na wszystkie czary szkoły ziemi",
|
||||
"core.bonus.EARTH_IMMUNITY.description": "Odporny na magię ziemi",
|
||||
"core.bonus.ENCHANTER.name": "Czarodziej",
|
||||
"core.bonus.ENCHANTER.description": "Rzuca czar ${subtype.spell}",
|
||||
"core.bonus.ENCHANTED.name": "Zaczarowany",
|
||||
@ -525,7 +556,7 @@
|
||||
"core.bonus.ENEMY_DEFENCE_REDUCTION.name": "Osłabienie Obrony (${val}%)",
|
||||
"core.bonus.ENEMY_DEFENCE_REDUCTION.description": "Osłabia obronę wroga podczas ataku",
|
||||
"core.bonus.FIRE_IMMUNITY.name": "Odporność: Ogień",
|
||||
"core.bonus.FIRE_IMMUNITY.description": "Odporny na wszystkie czary szkoły ognia",
|
||||
"core.bonus.FIRE_IMMUNITY.description": "Odporny na magię ognia",
|
||||
"core.bonus.FIRE_SHIELD.name": "Ognista tarcza (${val}%)",
|
||||
"core.bonus.FIRE_SHIELD.description": "Odbija część obrażeń z walki wręcz",
|
||||
"core.bonus.FIRST_STRIKE.name": "Pierwsze Uderzenie",
|
||||
@ -543,7 +574,7 @@
|
||||
"core.bonus.GARGOYLE.name": "Gargulec",
|
||||
"core.bonus.GARGOYLE.description": "Nie może się wskrzesić i uleczyć",
|
||||
"core.bonus.GENERAL_DAMAGE_REDUCTION.name": "Redukcja obrażeń (${val}%)",
|
||||
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Zmiejsza obrażenia fizyczne z dystansu lub walki wręcz",
|
||||
"core.bonus.GENERAL_DAMAGE_REDUCTION.description": "Zmiejsza obr. fizyczne z dystansu lub wręcz",
|
||||
"core.bonus.HATE.name": "${subtype.creature}",
|
||||
"core.bonus.HATE.description": "+${val}% dodatkowych obrażeń",
|
||||
"core.bonus.HEALER.name": "Uzdrowiciel",
|
||||
@ -556,8 +587,8 @@
|
||||
"core.bonus.KING.description": "czar POGROMCA stopnia ${val}+",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.name": "Odporność: Czary 1-${val}",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.description": "Odporny na czary 1-${val} poz.",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Ograniczony zasięg strzelania",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Nie może strzelać do celów będących dalej niż ${val} heksów",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Nie może strzelać do",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "celów będących dalej niż ${val} heksów",
|
||||
"core.bonus.LIFE_DRAIN.name": "Wysysa życie (${val}%)",
|
||||
"core.bonus.LIFE_DRAIN.description": "Wysysa ${val}% zadanych obrażeń",
|
||||
"core.bonus.MANA_CHANNELING.name": "Transfer many ${val}%",
|
||||
@ -591,13 +622,13 @@
|
||||
"core.bonus.RETURN_AFTER_STRIKE.name": "Atak i Powrót",
|
||||
"core.bonus.RETURN_AFTER_STRIKE.description": "Wraca po ataku wręcz",
|
||||
"core.bonus.REVENGE.name": "Odwet",
|
||||
"core.bonus.REVENGE.description": "Zadaje dodatkowe obrażenia zależne od strat własnych oddziału",
|
||||
"core.bonus.REVENGE.description": "Zadaje dodat. obr. zależne od strat własnych oddziału",
|
||||
"core.bonus.SHOOTER.name": "Dystansowy",
|
||||
"core.bonus.SHOOTER.description": "Stworzenie może strzelać",
|
||||
"core.bonus.SHOOTS_ALL_ADJACENT.name": "Ostrzeliwuje wszystko dookoła",
|
||||
"core.bonus.SHOOTS_ALL_ADJACENT.description": "Ataki dystansowe tego stworzenia uderzają we wszystkie cele na małym obszarze",
|
||||
"core.bonus.SHOOTS_ALL_ADJACENT.description": "Ataki dyst. tego stworzenia uderzają we wszystkie cele na małym obszarze",
|
||||
"core.bonus.SOUL_STEAL.name": "Kradzież dusz",
|
||||
"core.bonus.SOUL_STEAL.description": "Zdobywa ${val} nowych stworzeń za każdego zabitego wroga",
|
||||
"core.bonus.SOUL_STEAL.description": "+${val} nowych stworzeń za każdego zabitego wroga",
|
||||
"core.bonus.SPELLCASTER.name": "Czarodziej",
|
||||
"core.bonus.SPELLCASTER.description": "Może rzucić ${subtype.spell}",
|
||||
"core.bonus.SPELL_AFTER_ATTACK.name": "${val}% szans na czar",
|
||||
@ -629,5 +660,7 @@
|
||||
"core.bonus.WATER_IMMUNITY.name": "Odporność: Woda",
|
||||
"core.bonus.WATER_IMMUNITY.description": "Odporny na wszystkie czary szkoły wody",
|
||||
"core.bonus.WIDE_BREATH.name": "Szerokie zionięcie",
|
||||
"core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)"
|
||||
"core.bonus.WIDE_BREATH.description": "Szeroki atak zionięciem (wiele heksów)",
|
||||
"core.bonus.DISINTEGRATE.name": "Rozpadanie",
|
||||
"core.bonus.DISINTEGRATE.description": "Po śmierci nie pozostaje żaden trup"
|
||||
}
|
||||
|
@ -72,6 +72,11 @@
|
||||
"vcmi.lobby.noUnderground" : "sem subterrâneo",
|
||||
"vcmi.lobby.sortDate" : "Classifica mapas por data de alteração",
|
||||
"vcmi.lobby.backToLobby" : "Voltar para a sala de espera",
|
||||
"vcmi.lobby.author" : "Autor",
|
||||
"vcmi.lobby.handicap" : "Desvant.",
|
||||
"vcmi.lobby.handicap.resource" : "Fornece aos jogadores recursos apropriados para começar, além dos recursos iniciais normais. Valores negativos são permitidos, mas são limitados a 0 no total (o jogador nunca começa com recursos negativos).",
|
||||
"vcmi.lobby.handicap.income" : "Altera as várias rendas do jogador em porcentagem. Arredondado para cima.",
|
||||
"vcmi.lobby.handicap.growth" : "Altera a taxa de produção das criaturas nas cidades possuídas pelo jogador. Arredondado para cima.",
|
||||
|
||||
"vcmi.lobby.login.title" : "Sala de Espera Online do VCMI",
|
||||
"vcmi.lobby.login.username" : "Nome de usuário:",
|
||||
@ -157,6 +162,38 @@
|
||||
"vcmi.systemOptions.otherGroup" : "Outras Configurações", // não utilizado no momento
|
||||
"vcmi.systemOptions.townsGroup" : "Tela da Cidade",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Estatísticas",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Copiar dados",
|
||||
"vcmi.statisticWindow.selectView" : "Selecionar visualização",
|
||||
"vcmi.statisticWindow.value" : "Valor",
|
||||
"vcmi.statisticWindow.title.overview" : "Visão geral",
|
||||
"vcmi.statisticWindow.title.resources" : "Recursos",
|
||||
"vcmi.statisticWindow.title.income" : "Renda",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "Nº de heróis",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "Nº de cidades",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "Nº de artefatos",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "Nº de moradias",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "Nº de minas",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Força do exército",
|
||||
"vcmi.statisticWindow.title.experience" : "Experiência",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Custo do exército",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Custo de construção",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Exploração do mapa",
|
||||
"vcmi.statisticWindow.param.playerName" : "Nome do jogador",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Dias sobrevividos",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Nível máximo do herói",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Taxa de vitória (vs. herói)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Taxa de vitória (vs. neutro)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Batalhas (vs. herói)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Batalhas (vs. neutro)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Força máxima do exército",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Volume de comércio",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Obelisco visitado",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Cidade capturada",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Herói mais forte do oponente derrotado",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Graal encontrado",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Derrotado",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Tela Cheia (sem bordas)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Tela Cheia sem Bordas}\n\nSe selecionado, o VCMI será executado em modo de tela cheia sem bordas. Neste modo, o jogo sempre usará a mesma resolução que a área de trabalho, ignorando a resolução selecionada.",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Tela Cheia (exclusiva)",
|
||||
@ -287,17 +324,9 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "A construção base %s deve ser construída primeiro",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Não há criaturas para recrutar!",
|
||||
"vcmi.townHall.greetingManaVortex" : "Ao se aproximar de %s, seu corpo é preenchido com nova energia. Você dobrou seus pontos de mana normais.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Estudando os glifos de %s, você adquire uma visão dos segredos sobre o funcionamento de várias magias (+1 de Conhecimento).",
|
||||
"vcmi.townHall.greetingSpellPower" : "%s ensina novas maneiras de concentrar seus poderes mágicos (+1 de Força).",
|
||||
"vcmi.townHall.greetingExperience" : "Uma visita em %s ensina muitas habilidades novas (+1000 de Experiência).",
|
||||
"vcmi.townHall.greetingAttack" : "Algum tempo passado em %s permite que você aprenda habilidades de combate mais eficazes (+1 de Ataque).",
|
||||
"vcmi.townHall.greetingDefence" : "Ao passar um tempo em %s, os guerreiros experientes lá dentro te ensinam habilidades defensivas adicionais (+1 de Defesa).",
|
||||
"vcmi.townHall.hasNotProduced" : "%s ainda não produziu nada.",
|
||||
"vcmi.townHall.hasProduced" : "%s produziu %d %s nesta semana.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s dá +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " até a próxima batalha.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s restaurou seus pontos de mana para o máximo.",
|
||||
|
||||
"vcmi.townStructure.bank.borrow" : "Você entra no banco. Um banqueiro o vê e diz: \"Temos uma oferta especial para você. Você pode tomar um empréstimo de 2500 de ouro por 5 dias. Você terá que pagar 500 de ouro todos os dias.\"",
|
||||
"vcmi.townStructure.bank.payBack" : "Você entra no banco. Um banqueiro o vê e diz: \"Você já pegou um empréstimo. Pague-o antes de tomar um novo.\"",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Qualquer um dos seguintes:",
|
||||
"vcmi.logicalExpressions.allOf" : "Todos os seguintes:",
|
||||
|
@ -162,17 +162,6 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Сначала необходимо построить: %s",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Нет существ для найма!",
|
||||
"vcmi.townHall.greetingManaVortex" : "Близ %s ваше тело наполняется новой силой. Ваша обычная магическая энергия ныне удвоена.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Вы изучили знаки %s, на вас снизошло прозрение в деле магии (+1 Знания).",
|
||||
"vcmi.townHall.greetingSpellPower" : "В %s вас научили новым способам концентрации магической силы (+1 Силы)",
|
||||
"vcmi.townHall.greetingExperience" : "Посетив %s, вы узнали много нового (+1000 опыта).",
|
||||
"vcmi.townHall.greetingAttack" : "Пребывание в %s позволило вам лучше использовать боевые навыки (+1 Атаки).",
|
||||
"vcmi.townHall.greetingDefence" : "В %s искушенные воины преподали вам свои защитные умения (+1 Защиты).",
|
||||
"vcmi.townHall.hasNotProduced" : "В %s еще ничего не произведено.",
|
||||
"vcmi.townHall.hasProduced" : "В %s на этой неделе произведено: %d %s",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s дает вам +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " до следующей битвы.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s восстанавливает ваши очки заклинаний до максимума.",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Любое из:",
|
||||
"vcmi.logicalExpressions.allOf" : "Все перечисленное:",
|
||||
|
@ -219,17 +219,6 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Primero se debe construir el edificio base %s",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "¡No hay criaturas para reclutar!",
|
||||
"vcmi.townHall.greetingManaVortex" : "Al acercarte a %s, tu cuerpo se llena de nueva energía. Has duplicado tus puntos de hechizo normales.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Estudias los glifos en %s y obtienes una visión de los entresijos de varias magias (+1 conocimiento).",
|
||||
"vcmi.townHall.greetingSpellPower" : "El %s te enseña nuevas formas de enfocar tus poderes mágicos (+1 Poder).",
|
||||
"vcmi.townHall.greetingExperience" : "Una visita a %s te enseña muchas habilidades nuevas (+1000 Experiencia).",
|
||||
"vcmi.townHall.greetingAttack" : "El tiempo dedicado en %s te permite aprender habilidades de combate más efectivas (+1 habilidad de ataque).",
|
||||
"vcmi.townHall.greetingDefence" : "Pasando tiempo en %s, los guerreros experimentados allí te enseñan habilidades defensivas adicionales (+1 Defensa).",
|
||||
"vcmi.townHall.hasNotProduced" : "%s aún no ha producido nada.",
|
||||
"vcmi.townHall.hasProduced" : "%s ha producido %d %s esta semana.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s te da +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " hasta la próxima batalla.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s ha restaurado tus puntos de hechizo al máximo.",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Cualquiera de lo siguiente:",
|
||||
"vcmi.logicalExpressions.allOf" : "Todo lo siguiente:",
|
||||
|
@ -285,17 +285,6 @@
|
||||
|
||||
"vcmi.townHall.missingBase" : "Спочатку необхідно звести початкову будівлю: %s",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Немає істот, яких можна завербувати!",
|
||||
"vcmi.townHall.greetingManaVortex" : "Неподалік %s ваше тіло наповнюється новою силою. Ваша звична магічна енергія сьогодні подвоєна.",
|
||||
"vcmi.townHall.greetingKnowledge" : "Ви вивчили знаки на %s, і на вас зійшло прозріння у справах магії. (+1 Knowledge).",
|
||||
"vcmi.townHall.greetingSpellPower" : "В %s вас навчили новим методам концентрації магічної сили. (+1 Power).",
|
||||
"vcmi.townHall.greetingExperience" : "Відвідавши %s, ви дізналися багато нового. (+1000 Experience).",
|
||||
"vcmi.townHall.greetingAttack" : "Перебування у %s дозволило вам краще використовувати бойові навички (+1 Attack Skill).",
|
||||
"vcmi.townHall.greetingDefence" : "У %s досвідчені воїни виклали вам свої захисні вміння. (+1 Defense).",
|
||||
"vcmi.townHall.hasNotProduced" : "Поки що %s нічого не створило.",
|
||||
"vcmi.townHall.hasProduced" : "Цього тижня %s створило %d одиниць, цього разу це %s.",
|
||||
"vcmi.townHall.greetingCustomBonus" : "%s дає вам +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil" : " до наступної битви.",
|
||||
"vcmi.townHall.greetingInTownMagicWell" : "%s повністю відновлює ваш запас очків магії.",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Будь-що з перерахованого:",
|
||||
"vcmi.logicalExpressions.allOf" : "Все з перерахованого:",
|
||||
|
@ -159,17 +159,6 @@
|
||||
|
||||
"vcmi.townHall.missingBase": "Căn cứ %s phải được xây trước",
|
||||
"vcmi.townHall.noCreaturesToRecruit": "Không có quái để chiêu mộ!",
|
||||
"vcmi.townHall.greetingManaVortex": "%s giúp cơ thể bạn tràn đầy năng lượng mới. Bạn được gấp đôi năng lượng tối đa.",
|
||||
"vcmi.townHall.greetingKnowledge": "Bạn học chữ khắc trên %s và thấu hiểu cách vận hành của nhiều ma thuật (+1 Trí).",
|
||||
"vcmi.townHall.greetingSpellPower": "%s dạy bạn hướng mới tập trung sức mạnh ma thuật (+1 Lực).",
|
||||
"vcmi.townHall.greetingExperience": "Viếng thăm %s dạy bạn nhiều kĩ năng mới (+1000 Kinh nghiệm).",
|
||||
"vcmi.townHall.greetingAttack": "Thời gian ở %s giúp bạn học nhiều kĩ năng chiến đấu hiệu quả (+1 Công).",
|
||||
"vcmi.townHall.greetingDefence": "Thời gian ở %s, các chiến binh lão luyện tại đó dạy bạn nhiều kĩ năng phòng thủ (+1 Thủ).",
|
||||
"vcmi.townHall.hasNotProduced": "%s chưa tạo được cái gì.",
|
||||
"vcmi.townHall.hasProduced": "%s tạo %d %s tuần này.",
|
||||
"vcmi.townHall.greetingCustomBonus": "%s cho bạn +%d %s%s",
|
||||
"vcmi.townHall.greetingCustomUntil": " đến trận đánh tiếp theo.",
|
||||
"vcmi.townHall.greetingInTownMagicWell": "%s đã hồi phục năng lượng tối đa của bạn.",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf": "Bất kì cái sau:",
|
||||
"vcmi.logicalExpressions.allOf": "Tất cả cái sau:",
|
||||
|
@ -92,9 +92,9 @@ const ObstacleService * CGameInfo::obstacles() const
|
||||
return globalServices->obstacles();
|
||||
}
|
||||
|
||||
const IGameSettings * CGameInfo::settings() const
|
||||
const IGameSettings * CGameInfo::engineSettings() const
|
||||
{
|
||||
return globalServices->settings();
|
||||
return globalServices->engineSettings();
|
||||
}
|
||||
|
||||
spells::effects::Registry * CGameInfo::spellEffects()
|
||||
|
@ -70,7 +70,7 @@ public:
|
||||
const SkillService * skills() const override;
|
||||
const BattleFieldService * battlefields() const override;
|
||||
const ObstacleService * obstacles() const override;
|
||||
const IGameSettings * settings() const override;
|
||||
const IGameSettings * engineSettings() const override;
|
||||
|
||||
const spells::effects::Registry * spellEffects() const override;
|
||||
spells::effects::Registry * spellEffects() override;
|
||||
|
@ -20,8 +20,8 @@ extern SDL_Surface *screen; // main screen surface
|
||||
extern SDL_Surface *screen2; // and hlp surface (used to store not-active interfaces layer)
|
||||
extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
|
||||
|
||||
void handleQuit(bool ask = true);
|
||||
|
||||
/// Notify user about encountered fatal error and terminate the game
|
||||
/// Defined in clientapp EntryPoint
|
||||
/// TODO: decide on better location for this method
|
||||
[[noreturn]] void handleFatalError(const std::string & message, bool terminate);
|
||||
void handleQuit(bool ask = true);
|
||||
|
@ -1,4 +1,4 @@
|
||||
set(client_SRCS
|
||||
set(vcmiclientcommon_SRCS
|
||||
StdInc.cpp
|
||||
../CCallback.cpp
|
||||
|
||||
@ -27,6 +27,7 @@ set(client_SRCS
|
||||
battle/BattleStacksController.cpp
|
||||
battle/BattleWindow.cpp
|
||||
battle/CreatureAnimation.cpp
|
||||
battle/BattleOverlayLogVisualizer.cpp
|
||||
|
||||
eventsSDL/NotificationHandler.cpp
|
||||
eventsSDL/InputHandler.cpp
|
||||
@ -64,6 +65,7 @@ set(client_SRCS
|
||||
mainmenu/CPrologEpilogVideo.cpp
|
||||
mainmenu/CreditsScreen.cpp
|
||||
mainmenu/CHighScoreScreen.cpp
|
||||
mainmenu/CStatisticScreen.cpp
|
||||
|
||||
mapView/MapRenderer.cpp
|
||||
mapView/MapRendererContext.cpp
|
||||
@ -74,12 +76,14 @@ set(client_SRCS
|
||||
mapView/MapViewController.cpp
|
||||
mapView/MapViewModel.cpp
|
||||
mapView/mapHandler.cpp
|
||||
mapView/MapOverlayLogVisualizer.cpp
|
||||
|
||||
media/CAudioBase.cpp
|
||||
media/CMusicHandler.cpp
|
||||
media/CSoundHandler.cpp
|
||||
media/CVideoHandler.cpp
|
||||
|
||||
render/AssetGenerator.cpp
|
||||
render/CAnimation.cpp
|
||||
render/CBitmapHandler.cpp
|
||||
render/CDefFile.cpp
|
||||
@ -95,6 +99,7 @@ set(client_SRCS
|
||||
renderSDL/CTrueTypeFont.cpp
|
||||
renderSDL/CursorHardware.cpp
|
||||
renderSDL/CursorSoftware.cpp
|
||||
renderSDL/ImageScaled.cpp
|
||||
renderSDL/RenderHandler.cpp
|
||||
renderSDL/SDLImage.cpp
|
||||
renderSDL/SDLImageLoader.cpp
|
||||
@ -169,9 +174,10 @@ set(client_SRCS
|
||||
windows/settings/BattleOptionsTab.cpp
|
||||
windows/settings/AdventureOptionsTab.cpp
|
||||
|
||||
xBRZ/xbrz.cpp
|
||||
|
||||
ArtifactsUIController.cpp
|
||||
CGameInfo.cpp
|
||||
CMT.cpp
|
||||
CPlayerInterface.cpp
|
||||
PlayerLocalState.cpp
|
||||
CServerHandler.cpp
|
||||
@ -184,7 +190,7 @@ set(client_SRCS
|
||||
ServerRunner.cpp
|
||||
)
|
||||
|
||||
set(client_HEADERS
|
||||
set(vcmiclientcommon_HEADERS
|
||||
StdInc.h
|
||||
|
||||
adventureMap/AdventureMapInterface.h
|
||||
@ -214,6 +220,7 @@ set(client_HEADERS
|
||||
battle/BattleStacksController.h
|
||||
battle/BattleWindow.h
|
||||
battle/CreatureAnimation.h
|
||||
battle/BattleOverlayLogVisualizer.h
|
||||
|
||||
eventsSDL/NotificationHandler.h
|
||||
eventsSDL/InputHandler.h
|
||||
@ -254,6 +261,7 @@ set(client_HEADERS
|
||||
mainmenu/CPrologEpilogVideo.h
|
||||
mainmenu/CreditsScreen.h
|
||||
mainmenu/CHighScoreScreen.h
|
||||
mainmenu/CStatisticScreen.h
|
||||
|
||||
mapView/IMapRendererContext.h
|
||||
mapView/IMapRendererObserver.h
|
||||
@ -266,6 +274,7 @@ set(client_HEADERS
|
||||
mapView/MapViewController.h
|
||||
mapView/MapViewModel.h
|
||||
mapView/mapHandler.h
|
||||
mapView/MapOverlayLogVisualizer.h
|
||||
|
||||
media/CAudioBase.h
|
||||
media/CEmptyVideoPlayer.h
|
||||
@ -276,6 +285,7 @@ set(client_HEADERS
|
||||
media/ISoundPlayer.h
|
||||
media/IVideoPlayer.h
|
||||
|
||||
render/AssetGenerator.h
|
||||
render/CAnimation.h
|
||||
render/CBitmapHandler.h
|
||||
render/CDefFile.h
|
||||
@ -297,6 +307,7 @@ set(client_HEADERS
|
||||
renderSDL/CTrueTypeFont.h
|
||||
renderSDL/CursorHardware.h
|
||||
renderSDL/CursorSoftware.h
|
||||
renderSDL/ImageScaled.h
|
||||
renderSDL/RenderHandler.h
|
||||
renderSDL/SDLImage.h
|
||||
renderSDL/SDLImageLoader.h
|
||||
@ -374,6 +385,9 @@ set(client_HEADERS
|
||||
windows/settings/BattleOptionsTab.h
|
||||
windows/settings/AdventureOptionsTab.h
|
||||
|
||||
xBRZ/xbrz.h
|
||||
xBRZ/xbrz_tools.h
|
||||
|
||||
ArtifactsUIController.h
|
||||
CGameInfo.h
|
||||
CMT.h
|
||||
@ -392,76 +406,50 @@ set(client_HEADERS
|
||||
)
|
||||
|
||||
if(APPLE_IOS)
|
||||
set(client_SRCS ${client_SRCS}
|
||||
CFocusableHelper.cpp
|
||||
ios/GameChatKeyboardHandler.m
|
||||
ios/main.m
|
||||
ios/startSDL.mm
|
||||
set(vcmiclientcommon_SRCS ${vcmiclientcommon_SRCS}
|
||||
ios/utils.mm
|
||||
)
|
||||
set(client_HEADERS ${client_HEADERS}
|
||||
CFocusableHelper.h
|
||||
ios/GameChatKeyboardHandler.h
|
||||
ios/startSDL.h
|
||||
set(vcmiclientcommon_HEADERS ${vcmiclientcommon_HEADERS}
|
||||
ios/utils.h
|
||||
)
|
||||
endif()
|
||||
|
||||
assign_source_group(${client_SRCS} ${client_HEADERS} VCMI_client.rc)
|
||||
|
||||
if(ANDROID)
|
||||
add_library(vcmiclient SHARED ${client_SRCS} ${client_HEADERS})
|
||||
set_target_properties(vcmiclient PROPERTIES
|
||||
OUTPUT_NAME "vcmiclient_${ANDROID_ABI}" # required by Qt
|
||||
)
|
||||
else()
|
||||
add_executable(vcmiclient ${client_SRCS} ${client_HEADERS})
|
||||
endif()
|
||||
assign_source_group(${vcmiclientcommon_SRCS} ${vcmiclientcommon_HEADERS})
|
||||
add_library(vcmiclientcommon STATIC ${vcmiclientcommon_SRCS} ${vcmiclientcommon_HEADERS})
|
||||
|
||||
if(NOT ENABLE_STATIC_LIBS)
|
||||
add_dependencies(vcmiclient
|
||||
add_dependencies(vcmiclientcommon
|
||||
BattleAI
|
||||
EmptyAI
|
||||
StupidAI
|
||||
VCAI
|
||||
)
|
||||
if(ENABLE_NULLKILLER_AI)
|
||||
add_dependencies(vcmiclient Nullkiller)
|
||||
add_dependencies(vcmiclientcommon Nullkiller)
|
||||
endif()
|
||||
endif()
|
||||
if(APPLE_IOS)
|
||||
if(ENABLE_ERM)
|
||||
add_dependencies(vcmiclient vcmiERM)
|
||||
add_dependencies(vcmiclientcommon vcmiERM)
|
||||
endif()
|
||||
if(ENABLE_LUA)
|
||||
add_dependencies(vcmiclient vcmiLua)
|
||||
add_dependencies(vcmiclientcommon vcmiLua)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_sources(vcmiclient PRIVATE "VCMI_client.rc")
|
||||
set_target_properties(vcmiclient
|
||||
set_target_properties(vcmiclientcommon
|
||||
PROPERTIES
|
||||
OUTPUT_NAME "VCMI_client"
|
||||
PROJECT_LABEL "VCMI_client"
|
||||
OUTPUT_NAME "VCMI_vcmiclientcommon"
|
||||
PROJECT_LABEL "VCMI_vcmiclientcommon"
|
||||
)
|
||||
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclient)
|
||||
set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclientcommon)
|
||||
if(NOT ENABLE_DEBUG_CONSOLE)
|
||||
set_target_properties(vcmiclient PROPERTIES WIN32_EXECUTABLE)
|
||||
target_link_libraries(vcmiclient SDL2::SDL2main)
|
||||
endif()
|
||||
target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
|
||||
|
||||
# TODO: very hacky, find proper solution to copy AI dlls into bin dir
|
||||
if(MSVC)
|
||||
add_custom_command(TARGET vcmiclient POST_BUILD
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll
|
||||
COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
|
||||
)
|
||||
target_link_libraries(vcmiclientcommon SDL2::SDL2main)
|
||||
endif()
|
||||
target_compile_definitions(vcmiclientcommon PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
|
||||
elseif(APPLE_IOS)
|
||||
target_link_libraries(vcmiclient PRIVATE
|
||||
target_link_libraries(vcmiclientcommon PRIVATE
|
||||
iOS_utils
|
||||
|
||||
# FFmpeg
|
||||
@ -473,101 +461,31 @@ elseif(APPLE_IOS)
|
||||
"-framework CoreMedia"
|
||||
"-framework VideoToolbox"
|
||||
)
|
||||
|
||||
set_target_properties(vcmiclient PROPERTIES
|
||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/ios/Info.plist"
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "$(CODE_SIGNING_ALLOWED_FOR_APPS)"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
|
||||
)
|
||||
|
||||
foreach(XCODE_RESOURCE LaunchScreen.storyboard Images.xcassets Settings.bundle vcmi_logo.png)
|
||||
set(XCODE_RESOURCE_PATH ios/${XCODE_RESOURCE})
|
||||
target_sources(vcmiclient PRIVATE ${XCODE_RESOURCE_PATH})
|
||||
set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0" AND CMAKE_VERSION VERSION_LESS "3.25.0")
|
||||
set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main")
|
||||
endif()
|
||||
|
||||
target_link_libraries(vcmiclient PRIVATE vcmiservercommon)
|
||||
if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER)
|
||||
target_link_libraries(vcmiclient PRIVATE vcmilauncher)
|
||||
endif()
|
||||
target_link_libraries(vcmiclientcommon PRIVATE vcmiservercommon)
|
||||
|
||||
target_link_libraries(vcmiclient PRIVATE
|
||||
target_link_libraries(vcmiclientcommon PUBLIC
|
||||
vcmi SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF
|
||||
)
|
||||
|
||||
if(ffmpeg_LIBRARIES)
|
||||
target_link_libraries(vcmiclient PRIVATE
|
||||
target_link_libraries(vcmiclientcommon PRIVATE
|
||||
${ffmpeg_LIBRARIES}
|
||||
)
|
||||
else()
|
||||
target_compile_definitions(vcmiclient PRIVATE DISABLE_VIDEO)
|
||||
target_compile_definitions(vcmiclientcommon PRIVATE DISABLE_VIDEO)
|
||||
endif()
|
||||
|
||||
target_include_directories(vcmiclient PUBLIC
|
||||
target_include_directories(vcmiclientcommon PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
if (ffmpeg_INCLUDE_DIRS)
|
||||
target_include_directories(vcmiclient PRIVATE
|
||||
target_include_directories(vcmiclientcommon PRIVATE
|
||||
${ffmpeg_INCLUDE_DIRS}
|
||||
)
|
||||
endif()
|
||||
|
||||
vcmi_set_output_dir(vcmiclient "")
|
||||
enable_pch(vcmiclient)
|
||||
|
||||
if(APPLE_IOS)
|
||||
vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
|
||||
add_custom_command(TARGET vcmiclient POST_BUILD
|
||||
COMMAND ios/set_build_version.sh "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
|
||||
COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}" --config "$<CONFIG>" --prefix "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
|
||||
COMMAND ios/rpath_remove_symlinks.sh
|
||||
COMMAND ios/codesign.sh
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
install(TARGETS vcmiclient DESTINATION Payload COMPONENT app) # for ipa generation with cpack
|
||||
elseif(ANDROID)
|
||||
find_program(androidDeployQt androiddeployqt
|
||||
PATHS "${qtBinDir}"
|
||||
)
|
||||
vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
|
||||
|
||||
add_custom_target(android_deploy ALL
|
||||
COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --prefix "${androidQtBuildDir}"
|
||||
COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
|
||||
COMMAND_EXPAND_LISTS
|
||||
VERBATIM
|
||||
COMMENT "Create android package"
|
||||
)
|
||||
add_dependencies(android_deploy vcmiclient)
|
||||
else()
|
||||
install(TARGETS vcmiclient DESTINATION ${BIN_DIR})
|
||||
endif()
|
||||
|
||||
#install icons and desktop file on Linux
|
||||
if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
|
||||
#FIXME: move to client makefile?
|
||||
foreach(iconSize 16 22 32 48 64 128 256 512 1024 2048)
|
||||
install(FILES "icons/vcmiclient.${iconSize}x${iconSize}.png"
|
||||
DESTINATION "share/icons/hicolor/${iconSize}x${iconSize}/apps"
|
||||
RENAME vcmiclient.png
|
||||
)
|
||||
endforeach()
|
||||
|
||||
install(FILES icons/vcmiclient.svg
|
||||
DESTINATION share/icons/hicolor/scalable/apps
|
||||
RENAME vcmiclient.svg
|
||||
)
|
||||
install(FILES icons/vcmiclient.desktop
|
||||
DESTINATION share/applications
|
||||
)
|
||||
endif()
|
||||
vcmi_set_output_dir(vcmiclientcommon "")
|
||||
enable_pch(vcmiclientcommon)
|
||||
|
@ -13,7 +13,6 @@
|
||||
#include <vcmi/Artifact.h>
|
||||
|
||||
#include "CGameInfo.h"
|
||||
#include "CMT.h"
|
||||
#include "CServerHandler.h"
|
||||
#include "HeroMovementController.h"
|
||||
#include "PlayerLocalState.h"
|
||||
@ -100,9 +99,8 @@
|
||||
|
||||
#include "../lib/pathfinder/CGPathNode.h"
|
||||
|
||||
#include "../lib/serializer/BinaryDeserializer.h"
|
||||
#include "../lib/serializer/BinarySerializer.h"
|
||||
#include "../lib/serializer/CTypeList.h"
|
||||
#include "../lib/serializer/ESerializationVersion.h"
|
||||
|
||||
#include "../lib/spells/CSpellHandler.h"
|
||||
|
||||
@ -137,7 +135,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
|
||||
|
||||
{
|
||||
logGlobal->trace("\tHuman player interface for player %s being constructed", Player.toString());
|
||||
GH.defActionsDef = 0;
|
||||
LOCPLINT = this;
|
||||
playerID=Player;
|
||||
human=true;
|
||||
@ -171,13 +168,8 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
|
||||
adventureInt.reset(new AdventureMapInterface());
|
||||
}
|
||||
|
||||
void CPlayerInterface::playerEndsTurn(PlayerColor player)
|
||||
void CPlayerInterface::closeAllDialogs()
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
if (player == playerID)
|
||||
{
|
||||
makingTurn = false;
|
||||
|
||||
// remove all active dialogs that do not expect query answer
|
||||
for (;;)
|
||||
{
|
||||
@ -200,6 +192,15 @@ void CPlayerInterface::playerEndsTurn(PlayerColor player)
|
||||
castleInt->close();
|
||||
|
||||
castleInt = nullptr;
|
||||
}
|
||||
|
||||
void CPlayerInterface::playerEndsTurn(PlayerColor player)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
if (player == playerID)
|
||||
{
|
||||
makingTurn = false;
|
||||
closeAllDialogs();
|
||||
|
||||
// remove all pending dialogs that do not expect query answer
|
||||
vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
|
||||
@ -286,6 +287,7 @@ void CPlayerInterface::gamePause(bool pause)
|
||||
|
||||
void CPlayerInterface::yourTurn(QueryID queryID)
|
||||
{
|
||||
closeAllDialogs();
|
||||
CTutorialWindow::openWindowFirstTime(TutorialMode::TOUCH_ADVENTUREMAP);
|
||||
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
@ -620,12 +622,10 @@ void CPlayerInterface::battleStartBefore(const BattleID & battleID, const CCreat
|
||||
{
|
||||
movementController->onBattleStarted();
|
||||
|
||||
//Don't wait for dialogs when we are non-active hot-seat player
|
||||
if (LOCPLINT == this)
|
||||
waitForAllDialogs();
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed)
|
||||
void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
|
||||
@ -645,8 +645,6 @@ void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet
|
||||
cb->registerBattleInterface(autofightingAI);
|
||||
}
|
||||
|
||||
//Don't wait for dialogs when we are non-active hot-seat player
|
||||
if (LOCPLINT == this)
|
||||
waitForAllDialogs();
|
||||
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
@ -1014,7 +1012,7 @@ void CPlayerInterface::showInfoDialog(const std::string &text, const std::vector
|
||||
}
|
||||
std::shared_ptr<CInfoWindow> temp = CInfoWindow::create(text, playerID, components);
|
||||
|
||||
if (makingTurn && GH.windows().count() > 0 && LOCPLINT == this)
|
||||
if ((makingTurn || (battleInt && battleInt->curInt && battleInt->curInt.get() == this)) && GH.windows().count() > 0 && LOCPLINT == this)
|
||||
{
|
||||
CCS->soundh->playSound(static_cast<soundBase::soundID>(soundID));
|
||||
showingDialog->setBusy();
|
||||
@ -1146,7 +1144,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
|
||||
if(t)
|
||||
{
|
||||
auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
|
||||
image->scaleFast(Point(35, 23));
|
||||
image->scaleTo(Point(35, 23));
|
||||
images.push_back(image);
|
||||
}
|
||||
}
|
||||
@ -1477,7 +1475,7 @@ void CPlayerInterface::update()
|
||||
return;
|
||||
|
||||
//if there are any waiting dialogs, show them
|
||||
if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->isBusy())
|
||||
if (makingTurn && !dialogs.empty() && !showingDialog->isBusy())
|
||||
{
|
||||
showingDialog->setBusy();
|
||||
GH.windows().pushWindow(dialogs.front());
|
||||
@ -1633,30 +1631,30 @@ void CPlayerInterface::battleNewRoundFirst(const BattleID & battleID)
|
||||
battleInt->newRoundFirst();
|
||||
}
|
||||
|
||||
void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID)
|
||||
void CPlayerInterface::showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID)
|
||||
{
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
auto onWindowClosed = [this, queryID](){
|
||||
cb->selectionMade(0, queryID);
|
||||
};
|
||||
|
||||
if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && dynamic_cast<const CGArtifactsAltar*>(market) == nullptr)
|
||||
{
|
||||
// compatibility check, safe to remove for 1.6
|
||||
// 1.4 saves loaded in 1.5 will not be able to visit Altar of Sacrifice due to Altar now requiring different map object class
|
||||
static_assert(ESerializationVersion::RELEASE_143 < ESerializationVersion::CURRENT, "Please remove this compatibility check once it no longer needed");
|
||||
onWindowClosed();
|
||||
return;
|
||||
}
|
||||
|
||||
if(market->allowsTrade(EMarketMode::ARTIFACT_EXP) && visitor->getAlignment() != EAlignment::EVIL)
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, EMarketMode::ARTIFACT_EXP);
|
||||
else if(market->allowsTrade(EMarketMode::CREATURE_EXP) && visitor->getAlignment() != EAlignment::GOOD)
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, EMarketMode::CREATURE_EXP);
|
||||
else if(market->allowsTrade(EMarketMode::CREATURE_UNDEAD))
|
||||
GH.windows().createAndPushWindow<CTransformerWindow>(market, visitor, onWindowClosed);
|
||||
else if(!market->availableModes().empty())
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, market->availableModes().front());
|
||||
else if (!market->availableModes().empty())
|
||||
for(auto mode = EMarketMode::RESOURCE_RESOURCE; mode != EMarketMode::MARKET_AFTER_LAST_PLACEHOLDER; mode = vstd::next(mode, 1))
|
||||
{
|
||||
if(vstd::contains(market->availableModes(), mode))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(market, visitor, onWindowClosed, mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
onWindowClosed();
|
||||
}
|
||||
|
||||
void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID)
|
||||
@ -1665,7 +1663,7 @@ void CPlayerInterface::showUniversityWindow(const IMarket *market, const CGHeroI
|
||||
auto onWindowClosed = [this, queryID](){
|
||||
cb->selectionMade(0, queryID);
|
||||
};
|
||||
GH.windows().createAndPushWindow<CUniversityWindow>(visitor, market, onWindowClosed);
|
||||
GH.windows().createAndPushWindow<CUniversityWindow>(visitor, BuildingID::NONE, market, onWindowClosed);
|
||||
}
|
||||
|
||||
void CPlayerInterface::showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor)
|
||||
@ -1761,6 +1759,9 @@ void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
|
||||
|
||||
void CPlayerInterface::waitForAllDialogs()
|
||||
{
|
||||
if (!makingTurn)
|
||||
return;
|
||||
|
||||
while(!dialogs.empty())
|
||||
{
|
||||
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
|
||||
@ -1776,7 +1777,6 @@ void CPlayerInterface::proposeLoadingGame()
|
||||
[]()
|
||||
{
|
||||
CSH->endGameplay();
|
||||
GH.defActionsDef = 63;
|
||||
CMM->menu->switchToTab("load");
|
||||
},
|
||||
nullptr
|
||||
|
@ -120,7 +120,7 @@ protected: // Call-ins from server, should not be called directly, but only via
|
||||
void showTeleportDialog(const CGHeroInstance * hero, TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
|
||||
void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
|
||||
void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
|
||||
void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
|
||||
void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID) override;
|
||||
void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID) override;
|
||||
void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
|
||||
void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell
|
||||
@ -160,7 +160,7 @@ protected: // Call-ins from server, should not be called directly, but only via
|
||||
void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte) override; //various one-shot effect
|
||||
void battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged) override;
|
||||
void battleStartBefore(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
|
||||
void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
|
||||
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 battleUnitsChanged(const BattleID & battleID, const std::vector<UnitChanges> & units) override;
|
||||
void battleObstaclesChanged(const BattleID & battleID, const std::vector<ObstacleChanges> & obstacles) override;
|
||||
void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack
|
||||
@ -197,6 +197,7 @@ public: // public interface for use by client via LOCPLINT access
|
||||
void performAutosave();
|
||||
void gamePause(bool pause);
|
||||
void endNetwork();
|
||||
void closeAllDialogs();
|
||||
|
||||
///returns true if all events are processed internally
|
||||
bool capturedAllEvents();
|
||||
|
@ -21,7 +21,11 @@
|
||||
#include "globalLobby/GlobalLobbyClient.h"
|
||||
#include "lobby/CSelectionBase.h"
|
||||
#include "lobby/CLobbyScreen.h"
|
||||
#include "lobby/CBonusSelection.h"
|
||||
#include "windows/InfoWindows.h"
|
||||
#include "media/CMusicHandler.h"
|
||||
#include "media/IVideoPlayer.h"
|
||||
|
||||
|
||||
#include "mainmenu/CMainMenu.h"
|
||||
#include "mainmenu/CPrologEpilogVideo.h"
|
||||
@ -35,6 +39,8 @@
|
||||
#include "../lib/TurnTimerInfo.h"
|
||||
#include "../lib/VCMIDirs.h"
|
||||
#include "../lib/campaign/CampaignState.h"
|
||||
#include "../lib/gameState/CGameState.h"
|
||||
#include "../lib/gameState/HighScore.h"
|
||||
#include "../lib/CPlayerState.h"
|
||||
#include "../lib/mapping/CMapInfo.h"
|
||||
#include "../lib/mapObjects/CGTownInstance.h"
|
||||
@ -43,73 +49,16 @@
|
||||
#include "../lib/rmg/CMapGenOptions.h"
|
||||
#include "../lib/serializer/Connection.h"
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
|
||||
#include "../lib/serializer/CMemorySerializer.h"
|
||||
#include "../lib/UnlockGuard.h"
|
||||
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include "../lib/serializer/Cast.h"
|
||||
#include "LobbyClientNetPackVisitors.h"
|
||||
|
||||
#include <vcmi/events/EventBus.h>
|
||||
|
||||
template<typename T> class CApplyOnLobby;
|
||||
|
||||
class CBaseForLobbyApply
|
||||
{
|
||||
public:
|
||||
virtual bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const = 0;
|
||||
virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const = 0;
|
||||
virtual ~CBaseForLobbyApply(){};
|
||||
template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
|
||||
{
|
||||
return new CApplyOnLobby<U>();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
|
||||
{
|
||||
public:
|
||||
bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
|
||||
{
|
||||
auto & ref = static_cast<T&>(pack);
|
||||
ApplyOnLobbyHandlerNetPackVisitor visitor(*handler);
|
||||
|
||||
logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ref).name());
|
||||
ref.visit(visitor);
|
||||
|
||||
return visitor.getResult();
|
||||
}
|
||||
|
||||
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
|
||||
{
|
||||
auto & ref = static_cast<T &>(pack);
|
||||
ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby);
|
||||
|
||||
logNetwork->trace("\tApply on lobby from queue: %s", typeid(ref).name());
|
||||
ref.visit(visitor);
|
||||
}
|
||||
};
|
||||
|
||||
template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
|
||||
{
|
||||
public:
|
||||
bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
|
||||
{
|
||||
logGlobal->error("Cannot apply plain CPack!");
|
||||
assert(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
|
||||
{
|
||||
logGlobal->error("Cannot apply plain CPack!");
|
||||
assert(0);
|
||||
}
|
||||
};
|
||||
|
||||
CServerHandler::~CServerHandler()
|
||||
{
|
||||
if (serverRunner)
|
||||
@ -147,7 +96,6 @@ CServerHandler::CServerHandler()
|
||||
: networkHandler(INetworkHandler::createHandler())
|
||||
, lobbyClient(std::make_unique<GlobalLobbyClient>())
|
||||
, gameChat(std::make_unique<GameChatHandler>())
|
||||
, applier(std::make_unique<CApplier<CBaseForLobbyApply>>())
|
||||
, threadNetwork(&CServerHandler::threadRunNetwork, this)
|
||||
, state(EClientState::NONE)
|
||||
, serverPort(0)
|
||||
@ -158,12 +106,6 @@ CServerHandler::CServerHandler()
|
||||
, client(nullptr)
|
||||
{
|
||||
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
|
||||
registerTypesLobbyPacks(*applier);
|
||||
}
|
||||
|
||||
void CServerHandler::setHighScoreCalc(const std::shared_ptr<HighScoreCalculation> &newHighScoreCalc)
|
||||
{
|
||||
campaignScoreCalculator = newHighScoreCalc;
|
||||
}
|
||||
|
||||
void CServerHandler::threadRunNetwork()
|
||||
@ -324,8 +266,8 @@ void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netCon
|
||||
|
||||
void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack)
|
||||
{
|
||||
const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier
|
||||
apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
|
||||
ApplyOnLobbyScreenNetPackVisitor visitor(*this, dynamic_cast<CLobbyScreen *>(SEL));
|
||||
pack.visit(visitor);
|
||||
GH.windows().totalRedraw();
|
||||
}
|
||||
|
||||
@ -497,6 +439,14 @@ void CServerHandler::setPlayerName(PlayerColor color, const std::string & name)
|
||||
sendLobbyPack(lspn);
|
||||
}
|
||||
|
||||
void CServerHandler::setPlayerHandicap(PlayerColor color, Handicap handicap) const
|
||||
{
|
||||
LobbySetPlayerHandicap lsph;
|
||||
lsph.color = color;
|
||||
lsph.handicap = handicap;
|
||||
sendLobbyPack(lsph);
|
||||
}
|
||||
|
||||
void CServerHandler::setPlayerOption(ui8 what, int32_t value, PlayerColor player) const
|
||||
{
|
||||
LobbyChangePlayerOption lcpo;
|
||||
@ -585,6 +535,9 @@ void CServerHandler::sendGuiAction(ui8 action) const
|
||||
|
||||
void CServerHandler::sendRestartGame() const
|
||||
{
|
||||
if(si->campState && !si->campState->getLoadingBackground().empty())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>(si->campState->getLoadingBackground());
|
||||
else
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
|
||||
LobbyRestartGame endGame;
|
||||
@ -629,7 +582,12 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
|
||||
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
|
||||
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
{
|
||||
if(si->campState && !si->campState->getLoadingBackground().empty())
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>(si->campState->getLoadingBackground());
|
||||
else
|
||||
GH.windows().createAndPushWindow<CLoadingScreen>();
|
||||
}
|
||||
|
||||
LobbyPrepareStartGame lpsg;
|
||||
sendLobbyPack(lpsg);
|
||||
@ -655,7 +613,7 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
|
||||
break;
|
||||
case EStartMode::CAMPAIGN:
|
||||
if(si->campState->conqueredScenarios().empty())
|
||||
campaignScoreCalculator.reset();
|
||||
si->campState->highscoreParameters.clear();
|
||||
client->newGame(gameState);
|
||||
break;
|
||||
case EStartMode::LOAD_GAME:
|
||||
@ -669,43 +627,13 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
|
||||
setState(EClientState::GAMEPLAY);
|
||||
}
|
||||
|
||||
HighScoreParameter CServerHandler::prepareHighScores(PlayerColor player, bool victory)
|
||||
void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victory, const StatisticDataSet & statistic)
|
||||
{
|
||||
const auto * gs = client->gameState();
|
||||
const auto * playerState = gs->getPlayerState(player);
|
||||
|
||||
HighScoreParameter param;
|
||||
param.difficulty = gs->getStartInfo()->difficulty;
|
||||
param.day = gs->getDate();
|
||||
param.townAmount = gs->howManyTowns(player);
|
||||
param.usedCheat = gs->getPlayerState(player)->cheated;
|
||||
param.hasGrail = false;
|
||||
for(const CGHeroInstance * h : playerState->heroes)
|
||||
if(h->hasArt(ArtifactID::GRAIL))
|
||||
param.hasGrail = true;
|
||||
for(const CGTownInstance * t : playerState->towns)
|
||||
if(t->builtBuildings.count(BuildingID::GRAIL))
|
||||
param.hasGrail = true;
|
||||
param.allDefeated = true;
|
||||
for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer)
|
||||
{
|
||||
auto ps = gs->getPlayerState(otherPlayer, false);
|
||||
if(ps && otherPlayer != player && !ps->checkVanquished())
|
||||
param.allDefeated = false;
|
||||
}
|
||||
param.scenarioName = gs->getMapHeader()->name.toString();
|
||||
param.playerName = gs->getStartInfo()->playerInfos.find(player)->second.name;
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victory)
|
||||
{
|
||||
HighScoreParameter param = prepareHighScores(player, victory);
|
||||
HighScoreParameter param = HighScore::prepareHighScores(client->gameState(), player, victory);
|
||||
|
||||
if(victory && client->gameState()->getStartInfo()->campState)
|
||||
{
|
||||
startCampaignScenario(param, client->gameState()->getStartInfo()->campState);
|
||||
startCampaignScenario(param, client->gameState()->getStartInfo()->campState, statistic);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -714,9 +642,8 @@ void CServerHandler::showHighScoresAndEndGameplay(PlayerColor player, bool victo
|
||||
scenarioHighScores.isCampaign = false;
|
||||
|
||||
endGameplay();
|
||||
GH.defActionsDef = 63;
|
||||
CMM->menu->switchToTab("main");
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(victory, scenarioHighScores);
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(victory, scenarioHighScores, statistic);
|
||||
}
|
||||
}
|
||||
|
||||
@ -749,26 +676,23 @@ void CServerHandler::restartGameplay()
|
||||
logicConnection->enterLobbyConnectionMode();
|
||||
}
|
||||
|
||||
void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs)
|
||||
void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs, const StatisticDataSet & statistic)
|
||||
{
|
||||
std::shared_ptr<CampaignState> ourCampaign = cs;
|
||||
|
||||
if (!cs)
|
||||
ourCampaign = si->campState;
|
||||
|
||||
if(campaignScoreCalculator == nullptr)
|
||||
{
|
||||
campaignScoreCalculator = std::make_shared<HighScoreCalculation>();
|
||||
campaignScoreCalculator->isCampaign = true;
|
||||
campaignScoreCalculator->parameters.clear();
|
||||
}
|
||||
param.campaignName = cs->getNameTranslated();
|
||||
campaignScoreCalculator->parameters.push_back(param);
|
||||
cs->highscoreParameters.push_back(param);
|
||||
auto campaignScoreCalculator = std::make_shared<HighScoreCalculation>();
|
||||
campaignScoreCalculator->isCampaign = true;
|
||||
campaignScoreCalculator->parameters = cs->highscoreParameters;
|
||||
|
||||
endGameplay();
|
||||
|
||||
auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
|
||||
auto finisher = [this, ourCampaign]()
|
||||
auto finisher = [ourCampaign, campaignScoreCalculator, statistic]()
|
||||
{
|
||||
if(ourCampaign->campaignSet != "" && ourCampaign->isCampaignFinished())
|
||||
{
|
||||
@ -784,7 +708,15 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
|
||||
else
|
||||
{
|
||||
CMM->openCampaignScreen(ourCampaign->campaignSet);
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator);
|
||||
if(!ourCampaign->getOutroVideo().empty() && CCS->videoh->open(ourCampaign->getOutroVideo(), false))
|
||||
{
|
||||
CCS->musich->stopMusic();
|
||||
GH.windows().createAndPushWindow<CampaignRimVideo>(ourCampaign->getOutroVideo(), ourCampaign->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : ourCampaign->getVideoRim(), [campaignScoreCalculator, statistic](){
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
|
||||
});
|
||||
}
|
||||
else
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
|
||||
}
|
||||
};
|
||||
|
||||
@ -948,7 +880,6 @@ void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> &
|
||||
if(client)
|
||||
{
|
||||
endGameplay();
|
||||
GH.defActionsDef = 63;
|
||||
CMM->menu->switchToTab("main");
|
||||
showServerError(CGI->generaltexth->translate("vcmi.server.errors.disconnected"));
|
||||
}
|
||||
@ -991,7 +922,10 @@ void CServerHandler::waitForServerShutdown()
|
||||
|
||||
void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
|
||||
{
|
||||
if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, lobbyPack))
|
||||
ApplyOnLobbyHandlerNetPackVisitor visitor(*this);
|
||||
lobbyPack.visit(visitor);
|
||||
|
||||
if(visitor.getResult())
|
||||
{
|
||||
if(!settings["session"]["headless"].Bool())
|
||||
applyPackOnLobbyScreen(lobbyPack);
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "../lib/network/NetworkInterface.h"
|
||||
#include "../lib/StartInfo.h"
|
||||
#include "../lib/gameState/GameStatistics.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -28,7 +29,7 @@ struct CPack;
|
||||
struct CPackForLobby;
|
||||
struct CPackForClient;
|
||||
|
||||
template<typename T> class CApplier;
|
||||
class HighScoreParameter;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
@ -38,9 +39,6 @@ class GlobalLobbyClient;
|
||||
class GameChatHandler;
|
||||
class IServerRunner;
|
||||
|
||||
class HighScoreCalculation;
|
||||
class HighScoreParameter;
|
||||
|
||||
enum class ESelectionScreen : ui8;
|
||||
enum class ELoadMode : ui8;
|
||||
|
||||
@ -81,6 +79,7 @@ public:
|
||||
virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
|
||||
virtual void setPlayer(PlayerColor color) const = 0;
|
||||
virtual void setPlayerName(PlayerColor color, const std::string & name) const = 0;
|
||||
virtual void setPlayerHandicap(PlayerColor color, Handicap handicap) const = 0;
|
||||
virtual void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const = 0;
|
||||
virtual void setDifficulty(int to) const = 0;
|
||||
virtual void setTurnTimerInfo(const TurnTimerInfo &) const = 0;
|
||||
@ -101,11 +100,9 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
|
||||
std::shared_ptr<INetworkConnection> networkConnection;
|
||||
std::unique_ptr<GlobalLobbyClient> lobbyClient;
|
||||
std::unique_ptr<GameChatHandler> gameChat;
|
||||
std::unique_ptr<CApplier<CBaseForLobbyApply>> applier;
|
||||
std::unique_ptr<IServerRunner> serverRunner;
|
||||
std::shared_ptr<CMapInfo> mapToStart;
|
||||
std::vector<std::string> localPlayerNames;
|
||||
std::shared_ptr<HighScoreCalculation> campaignScoreCalculator;
|
||||
|
||||
boost::thread threadNetwork;
|
||||
|
||||
@ -127,8 +124,6 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
|
||||
|
||||
bool isServerLocal() const;
|
||||
|
||||
HighScoreParameter prepareHighScores(PlayerColor player, bool victory);
|
||||
|
||||
public:
|
||||
/// High-level connection overlay that is capable of (de)serializing network data
|
||||
std::shared_ptr<CConnection> logicConnection;
|
||||
@ -191,6 +186,7 @@ public:
|
||||
void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
|
||||
void setPlayer(PlayerColor color) const override;
|
||||
void setPlayerName(PlayerColor color, const std::string & name) const override;
|
||||
void setPlayerHandicap(PlayerColor color, Handicap handicap) const override;
|
||||
void setPlayerOption(ui8 what, int32_t value, PlayerColor player) const override;
|
||||
void setDifficulty(int to) const override;
|
||||
void setTurnTimerInfo(const TurnTimerInfo &) const override;
|
||||
@ -206,11 +202,11 @@ public:
|
||||
void debugStartTest(std::string filename, bool save = false);
|
||||
|
||||
void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
|
||||
void showHighScoresAndEndGameplay(PlayerColor player, bool victory);
|
||||
void showHighScoresAndEndGameplay(PlayerColor player, bool victory, const StatisticDataSet & statistic);
|
||||
void endNetwork();
|
||||
void endGameplay();
|
||||
void restartGameplay();
|
||||
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs = {});
|
||||
void startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs, const StatisticDataSet & statistic);
|
||||
void showServerError(const std::string & txt) const;
|
||||
|
||||
// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle
|
||||
@ -219,7 +215,6 @@ public:
|
||||
|
||||
void visitForLobby(CPackForLobby & lobbyPack);
|
||||
void visitForClient(CPackForClient & clientPack);
|
||||
void setHighScoreCalc(const std::shared_ptr<HighScoreCalculation> &newHighScoreCalc);
|
||||
};
|
||||
|
||||
extern CServerHandler * CSH;
|
||||
|
@ -29,13 +29,10 @@
|
||||
#include "../lib/VCMIDirs.h"
|
||||
#include "../lib/UnlockGuard.h"
|
||||
#include "../lib/battle/BattleInfo.h"
|
||||
#include "../lib/serializer/BinaryDeserializer.h"
|
||||
#include "../lib/serializer/BinarySerializer.h"
|
||||
#include "../lib/serializer/Connection.h"
|
||||
#include "../lib/mapping/CMapService.h"
|
||||
#include "../lib/pathfinder/CGPathNode.h"
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/registerTypes/RegisterTypesClientPacks.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vcmi/events/EventBus.h>
|
||||
@ -50,53 +47,6 @@
|
||||
|
||||
ThreadSafeVector<int> CClient::waitingRequest;
|
||||
|
||||
template<typename T> class CApplyOnCL;
|
||||
|
||||
class CBaseForCLApply
|
||||
{
|
||||
public:
|
||||
virtual void applyOnClAfter(CClient * cl, CPack * pack) const =0;
|
||||
virtual void applyOnClBefore(CClient * cl, CPack * pack) const =0;
|
||||
virtual ~CBaseForCLApply(){}
|
||||
|
||||
template<typename U> static CBaseForCLApply * getApplier(const U * t = nullptr)
|
||||
{
|
||||
return new CApplyOnCL<U>();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> class CApplyOnCL : public CBaseForCLApply
|
||||
{
|
||||
public:
|
||||
void applyOnClAfter(CClient * cl, CPack * pack) const override
|
||||
{
|
||||
T * ptr = static_cast<T *>(pack);
|
||||
ApplyClientNetPackVisitor visitor(*cl, *cl->gameState());
|
||||
ptr->visit(visitor);
|
||||
}
|
||||
void applyOnClBefore(CClient * cl, CPack * pack) const override
|
||||
{
|
||||
T * ptr = static_cast<T *>(pack);
|
||||
ApplyFirstClientNetPackVisitor visitor(*cl, *cl->gameState());
|
||||
ptr->visit(visitor);
|
||||
}
|
||||
};
|
||||
|
||||
template<> class CApplyOnCL<CPack>: public CBaseForCLApply
|
||||
{
|
||||
public:
|
||||
void applyOnClAfter(CClient * cl, CPack * pack) const override
|
||||
{
|
||||
logGlobal->error("Cannot apply on CL plain CPack!");
|
||||
assert(0);
|
||||
}
|
||||
void applyOnClBefore(CClient * cl, CPack * pack) const override
|
||||
{
|
||||
logGlobal->error("Cannot apply on CL plain CPack!");
|
||||
assert(0);
|
||||
}
|
||||
};
|
||||
|
||||
CPlayerEnvironment::CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_)
|
||||
: player(player_),
|
||||
cl(cl_),
|
||||
@ -130,12 +80,9 @@ const CPlayerEnvironment::GameCb * CPlayerEnvironment::game() const
|
||||
return mainCallback.get();
|
||||
}
|
||||
|
||||
|
||||
CClient::CClient()
|
||||
{
|
||||
waitingRequest.clear();
|
||||
applier = std::make_shared<CApplier<CBaseForCLApply>>();
|
||||
registerTypesClientPacks(*applier);
|
||||
gs = nullptr;
|
||||
}
|
||||
|
||||
@ -400,25 +347,21 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
|
||||
}
|
||||
}
|
||||
|
||||
void CClient::handlePack(CPack * pack)
|
||||
void CClient::handlePack(CPackForClient * pack)
|
||||
{
|
||||
CBaseForCLApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier
|
||||
if(apply)
|
||||
{
|
||||
apply->applyOnClBefore(this, pack);
|
||||
ApplyClientNetPackVisitor afterVisitor(*this, *gameState());
|
||||
ApplyFirstClientNetPackVisitor beforeVisitor(*this, *gameState());
|
||||
|
||||
pack->visit(beforeVisitor);
|
||||
logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
|
||||
{
|
||||
boost::unique_lock lock(CGameState::mutex);
|
||||
gs->apply(pack);
|
||||
}
|
||||
logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
|
||||
apply->applyOnClAfter(this, pack);
|
||||
pack->visit(afterVisitor);
|
||||
logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
|
||||
}
|
||||
else
|
||||
{
|
||||
logNetwork->error("Message %s cannot be applied, cannot find applier!", typeid(*pack).name());
|
||||
}
|
||||
|
||||
delete pack;
|
||||
}
|
||||
|
||||
@ -443,8 +386,8 @@ void CClient::battleStarted(const BattleInfo * info)
|
||||
{
|
||||
std::shared_ptr<CPlayerInterface> att;
|
||||
std::shared_ptr<CPlayerInterface> def;
|
||||
auto & leftSide = info->sides[0];
|
||||
auto & rightSide = info->sides[1];
|
||||
const auto & leftSide = info->getSide(BattleSide::LEFT_SIDE);
|
||||
const auto & rightSide = info->getSide(BattleSide::RIGHT_SIDE);
|
||||
|
||||
for(auto & battleCb : battleCallbacks)
|
||||
{
|
||||
@ -453,17 +396,17 @@ void CClient::battleStarted(const BattleInfo * info)
|
||||
}
|
||||
|
||||
//If quick combat is not, do not prepare interfaces for battleint
|
||||
auto callBattleStart = [&](PlayerColor color, ui8 side)
|
||||
auto callBattleStart = [&](PlayerColor color, BattleSide side)
|
||||
{
|
||||
if(vstd::contains(battleints, color))
|
||||
battleints[color]->battleStart(info->battleID, leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side, info->replayAllowed);
|
||||
};
|
||||
|
||||
callBattleStart(leftSide.color, 0);
|
||||
callBattleStart(rightSide.color, 1);
|
||||
callBattleStart(PlayerColor::UNFLAGGABLE, 1);
|
||||
callBattleStart(leftSide.color, BattleSide::LEFT_SIDE);
|
||||
callBattleStart(rightSide.color, BattleSide::RIGHT_SIDE);
|
||||
callBattleStart(PlayerColor::UNFLAGGABLE, BattleSide::RIGHT_SIDE);
|
||||
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
|
||||
callBattleStart(PlayerColor::SPECTATOR, 1);
|
||||
callBattleStart(PlayerColor::SPECTATOR, BattleSide::RIGHT_SIDE);
|
||||
|
||||
if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
|
||||
att = std::dynamic_pointer_cast<CPlayerInterface>(playerint[leftSide.color]);
|
||||
@ -480,9 +423,9 @@ void CClient::battleStarted(const BattleInfo * info)
|
||||
{
|
||||
auto side = interface->cb->getBattle(info->battleID)->playerToSide(interface->playerID);
|
||||
|
||||
if(interface->playerID == info->sides[info->tacticsSide].color)
|
||||
if(interface->playerID == info->getSide(info->tacticsSide).color)
|
||||
{
|
||||
auto action = BattleAction::makeEndOFTacticPhase(*side);
|
||||
auto action = BattleAction::makeEndOFTacticPhase(side);
|
||||
interface->cb->battleMakeTacticAction(info->battleID, action);
|
||||
}
|
||||
}
|
||||
@ -514,7 +457,7 @@ void CClient::battleStarted(const BattleInfo * info)
|
||||
|
||||
if(info->tacticDistance)
|
||||
{
|
||||
auto tacticianColor = info->sides[info->tacticsSide].color;
|
||||
auto tacticianColor = info->getSide(info->tacticsSide).color;
|
||||
|
||||
if (vstd::contains(battleints, tacticianColor))
|
||||
battleints[tacticianColor]->yourTacticPhase(info->battleID, info->tacticDistance);
|
||||
@ -523,9 +466,11 @@ void CClient::battleStarted(const BattleInfo * info)
|
||||
|
||||
void CClient::battleFinished(const BattleID & battleID)
|
||||
{
|
||||
for(auto & side : gs->getBattle(battleID)->sides)
|
||||
if(battleCallbacks.count(side.color))
|
||||
battleCallbacks[side.color]->onBattleEnded(battleID);
|
||||
for(auto side : { BattleSide::ATTACKER, BattleSide::DEFENDER })
|
||||
{
|
||||
if(battleCallbacks.count(gs->getBattle(battleID)->getSide(side).color))
|
||||
battleCallbacks[gs->getBattle(battleID)->getSide(side).color]->onBattleEnded(battleID);
|
||||
}
|
||||
|
||||
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
|
||||
battleCallbacks[PlayerColor::SPECTATOR]->onBattleEnded(battleID);
|
||||
|
@ -21,14 +21,10 @@ struct CPackForServer;
|
||||
class IBattleEventsReceiver;
|
||||
class CBattleGameInterface;
|
||||
class CGameInterface;
|
||||
class BinaryDeserializer;
|
||||
class BinarySerializer;
|
||||
class BattleAction;
|
||||
class BattleInfo;
|
||||
struct BankConfig;
|
||||
|
||||
template<typename T> class CApplier;
|
||||
|
||||
#if SCRIPTING_ENABLED
|
||||
namespace scripting
|
||||
{
|
||||
@ -147,7 +143,7 @@ public:
|
||||
|
||||
static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
|
||||
|
||||
void handlePack(CPack * pack); //applies the given pack and deletes it
|
||||
void handlePack(CPackForClient * pack); //applies the given pack and deletes it
|
||||
int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
|
||||
|
||||
void battleStarted(const BattleInfo * info);
|
||||
@ -168,7 +164,7 @@ public:
|
||||
void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs = false) override {};
|
||||
void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs = false) override {};
|
||||
|
||||
void showBlockingDialog(BlockingDialog * iw) override {};
|
||||
void showBlockingDialog(const IObjectInterface * caller, BlockingDialog * iw) override {};
|
||||
void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {};
|
||||
void showTeleportDialog(TeleportDialog * iw) override {};
|
||||
void showObjectWindow(const CGObjectInstance * object, EOpenWindowMode window, const CGHeroInstance * visitor, bool addQuery) override {};
|
||||
@ -188,7 +184,8 @@ public:
|
||||
|
||||
void removeAfterVisit(const CGObjectInstance * object) override {};
|
||||
bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;};
|
||||
bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos) override {return false;}
|
||||
bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) override {return false;};
|
||||
bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) override {return false;};
|
||||
bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional<bool> askAssemble) override {return false;};
|
||||
void removeArtifact(const ArtifactLocation & al) override {};
|
||||
bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;};
|
||||
@ -196,9 +193,8 @@ public:
|
||||
void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
|
||||
void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
|
||||
void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
|
||||
void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero
|
||||
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
|
||||
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
void startBattle(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, const BattleLayout & layout, const CGTownInstance * town) override {}; //use hero=nullptr for no hero
|
||||
void startBattle(const CArmedInstance * army1, const CArmedInstance * army2) override {}; //if any of armies is hero, hero will be used
|
||||
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
|
||||
void giveHeroBonus(GiveBonus * bonus) override {};
|
||||
void setMovePoints(SetMovePoints * smp) override {};
|
||||
@ -211,7 +207,7 @@ public:
|
||||
void castSpell(const spells::Caster * caster, SpellID spellID, const int3 &pos) override {};
|
||||
|
||||
void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, ETileVisibility mode) override {}
|
||||
void changeFogOfWar(std::unordered_set<int3> & tiles, PlayerColor player, ETileVisibility mode) override {}
|
||||
void changeFogOfWar(const std::unordered_set<int3> & tiles, PlayerColor player, ETileVisibility mode) override {}
|
||||
|
||||
void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value) override {};
|
||||
void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override {};
|
||||
@ -237,8 +233,6 @@ private:
|
||||
#endif
|
||||
std::unique_ptr<events::EventBus> clientEventBus;
|
||||
|
||||
std::shared_ptr<CApplier<CBaseForCLApply>> applier;
|
||||
|
||||
mutable boost::mutex pathCacheMutex;
|
||||
std::map<const CGHeroInstance *, std::shared_ptr<CPathsInfo>> pathCache;
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "gui/CGuiHandler.h"
|
||||
#include "gui/WindowHandler.h"
|
||||
#include "render/IRenderHandler.h"
|
||||
#include "render/AssetGenerator.h"
|
||||
#include "ClientNetPackVisitors.h"
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/gameState/CGameState.h"
|
||||
@ -38,7 +39,6 @@
|
||||
#include "../lib/CHeroHandler.h"
|
||||
#include "../lib/VCMIDirs.h"
|
||||
#include "../lib/logging/VisualLogger.h"
|
||||
#include "CMT.h"
|
||||
#include "../lib/serializer/Connection.h"
|
||||
|
||||
#ifdef SCRIPTING_ENABLED
|
||||
@ -502,6 +502,12 @@ void ClientCommandManager::handleVsLog(std::istringstream & singleWordBuffer)
|
||||
logVisual->setKey(key);
|
||||
}
|
||||
|
||||
void ClientCommandManager::handleGenerateAssets()
|
||||
{
|
||||
AssetGenerator::generateAll();
|
||||
printCommandMessage("All assets generated");
|
||||
}
|
||||
|
||||
void ClientCommandManager::printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType)
|
||||
{
|
||||
switch(messageType)
|
||||
@ -624,6 +630,9 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
|
||||
else if(commandName == "vslog")
|
||||
handleVsLog(singleWordBuffer);
|
||||
|
||||
else if(message=="generate assets")
|
||||
handleGenerateAssets();
|
||||
|
||||
else
|
||||
{
|
||||
if (!commandName.empty() && !vstd::iswithin(commandName[0], 0, ' ')) // filter-out debugger/IDE noise
|
||||
|
@ -84,6 +84,9 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
|
||||
// shows object graph
|
||||
void handleVsLog(std::istringstream & singleWordBuffer);
|
||||
|
||||
// generate all assets
|
||||
void handleGenerateAssets();
|
||||
|
||||
// Prints in Chat the given message
|
||||
void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET);
|
||||
void giveTurn(const PlayerColor &color);
|
||||
|
@ -47,7 +47,7 @@ public:
|
||||
void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) override;
|
||||
void visitBulkSmartRebalanceStacks(BulkSmartRebalanceStacks & pack) override;
|
||||
void visitPutArtifact(PutArtifact & pack) override;
|
||||
void visitEraseArtifact(EraseArtifact & pack) override;
|
||||
void visitEraseArtifact(BulkEraseArtifacts & pack) override;
|
||||
void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
|
||||
void visitAssembledArtifact(AssembledArtifact & pack) override;
|
||||
void visitDisassembledArtifact(DisassembledArtifact & pack) override;
|
||||
|
@ -29,7 +29,6 @@
|
||||
#include "../CCallback.h"
|
||||
#include "../lib/filesystem/Filesystem.h"
|
||||
#include "../lib/filesystem/FileInfo.h"
|
||||
#include "../lib/serializer/BinarySerializer.h"
|
||||
#include "../lib/serializer/Connection.h"
|
||||
#include "../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../lib/CHeroHandler.h"
|
||||
@ -108,8 +107,8 @@ void callBattleInterfaceIfPresentForBothSides(CClient & cl, const BattleID & bat
|
||||
return;
|
||||
}
|
||||
|
||||
callOnlyThatBattleInterface(cl, cl.gameState()->getBattle(battleID)->sides[0].color, ptr, std::forward<Args2>(args)...);
|
||||
callOnlyThatBattleInterface(cl, cl.gameState()->getBattle(battleID)->sides[1].color, ptr, std::forward<Args2>(args)...);
|
||||
callOnlyThatBattleInterface(cl, cl.gameState()->getBattle(battleID)->getSide(BattleSide::ATTACKER).color, ptr, std::forward<Args2>(args)...);
|
||||
callOnlyThatBattleInterface(cl, cl.gameState()->getBattle(battleID)->getSide(BattleSide::DEFENDER).color, ptr, std::forward<Args2>(args)...);
|
||||
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool() && LOCPLINT->battleInt)
|
||||
{
|
||||
callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, ptr, std::forward<Args2>(args)...);
|
||||
@ -291,9 +290,10 @@ void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
|
||||
callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::askToAssembleArtifact, pack.al);
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
|
||||
void ApplyClientNetPackVisitor::visitEraseArtifact(BulkEraseArtifacts & pack)
|
||||
{
|
||||
callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactRemoved, pack.al);
|
||||
for(const auto & slotErase : pack.posPack)
|
||||
callInterfaceIfPresent(cl, cl.getOwner(pack.artHolder), &IGameEventsReceiver::artifactRemoved, ArtifactLocation(pack.artHolder, slotErase));
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
|
||||
@ -362,6 +362,14 @@ void ApplyClientNetPackVisitor::visitHeroVisit(HeroVisit & pack)
|
||||
void ApplyClientNetPackVisitor::visitNewTurn(NewTurn & pack)
|
||||
{
|
||||
cl.invalidatePaths();
|
||||
|
||||
if (pack.newWeekNotification)
|
||||
{
|
||||
const auto & newWeek = *pack.newWeekNotification;
|
||||
|
||||
std::string str = newWeek.text.toString();
|
||||
callAllInterfaces(cl, &CGameInterface::showInfoDialog, newWeek.type, str, newWeek.components,(soundBase::soundID)newWeek.soundID);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitGiveBonus(GiveBonus & pack)
|
||||
@ -420,7 +428,7 @@ void ApplyClientNetPackVisitor::visitPlayerEndsGame(PlayerEndsGame & pack)
|
||||
adventureInt.reset();
|
||||
}
|
||||
|
||||
CSH->showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory());
|
||||
CSH->showHighScoresAndEndGameplay(pack.player, pack.victoryLossCheckResult.victory(), pack.statistic);
|
||||
}
|
||||
|
||||
// In auto testing pack.mode we always close client if red pack.player won or lose
|
||||
@ -769,12 +777,12 @@ void ApplyClientNetPackVisitor::visitMapObjectSelectDialog(MapObjectSelectDialog
|
||||
void ApplyFirstClientNetPackVisitor::visitBattleStart(BattleStart & pack)
|
||||
{
|
||||
// Cannot use the usual code because curB is not set yet
|
||||
callOnlyThatBattleInterface(cl, pack.info->sides[0].color, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
|
||||
pack.info->tile, pack.info->sides[0].hero, pack.info->sides[1].hero);
|
||||
callOnlyThatBattleInterface(cl, pack.info->sides[1].color, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
|
||||
pack.info->tile, pack.info->sides[0].hero, pack.info->sides[1].hero);
|
||||
callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
|
||||
pack.info->tile, pack.info->sides[0].hero, pack.info->sides[1].hero);
|
||||
callOnlyThatBattleInterface(cl, pack.info->getSide(BattleSide::ATTACKER).color, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->getSide(BattleSide::ATTACKER).armyObject, pack.info->getSide(BattleSide::DEFENDER).armyObject,
|
||||
pack.info->tile, pack.info->getSide(BattleSide::ATTACKER).hero, pack.info->getSide(BattleSide::DEFENDER).hero);
|
||||
callOnlyThatBattleInterface(cl, pack.info->getSide(BattleSide::DEFENDER).color, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->getSide(BattleSide::ATTACKER).armyObject, pack.info->getSide(BattleSide::DEFENDER).armyObject,
|
||||
pack.info->tile, pack.info->getSide(BattleSide::ATTACKER).hero, pack.info->getSide(BattleSide::DEFENDER).hero);
|
||||
callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->getSide(BattleSide::ATTACKER).armyObject, pack.info->getSide(BattleSide::DEFENDER).armyObject,
|
||||
pack.info->tile, pack.info->getSide(BattleSide::ATTACKER).hero, pack.info->getSide(BattleSide::DEFENDER).hero);
|
||||
}
|
||||
|
||||
void ApplyClientNetPackVisitor::visitBattleStart(BattleStart & pack)
|
||||
@ -801,9 +809,9 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack &
|
||||
PlayerColor playerToCall; //pack.player that will move activated stack
|
||||
if (activated->hasBonusOfType(BonusType::HYPNOTIZED))
|
||||
{
|
||||
playerToCall = (gs.getBattle(pack.battleID)->sides[0].color == activated->unitOwner()
|
||||
? gs.getBattle(pack.battleID)->sides[1].color
|
||||
: gs.getBattle(pack.battleID)->sides[0].color);
|
||||
playerToCall = gs.getBattle(pack.battleID)->getSide(BattleSide::ATTACKER).color == activated->unitOwner()
|
||||
? gs.getBattle(pack.battleID)->getSide(BattleSide::DEFENDER).color
|
||||
: gs.getBattle(pack.battleID)->getSide(BattleSide::ATTACKER).color;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -999,7 +1007,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
|
||||
case EOpenWindowMode::UNIVERSITY_WINDOW:
|
||||
{
|
||||
//displays University window (when hero enters University on adventure map)
|
||||
const auto * market = dynamic_cast<const IMarket*>(cl.getObj(ObjectInstanceID(pack.object)));
|
||||
const auto * market = cl.getMarket(ObjectInstanceID(pack.object));
|
||||
const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
|
||||
callInterfaceIfPresent(cl, hero->tempOwner, &IGameEventsReceiver::showUniversityWindow, market, hero, pack.queryID);
|
||||
}
|
||||
@ -1009,7 +1017,7 @@ void ApplyClientNetPackVisitor::visitOpenWindow(OpenWindow & pack)
|
||||
//displays Thieves' Guild window (when hero enters Den of Thieves)
|
||||
const CGObjectInstance *obj = cl.getObj(ObjectInstanceID(pack.object));
|
||||
const CGHeroInstance *hero = cl.getHero(ObjectInstanceID(pack.visitor));
|
||||
const auto *market = dynamic_cast<const IMarket*>(obj);
|
||||
const auto market = cl.getMarket(pack.object);
|
||||
callInterfaceIfPresent(cl, cl.getTile(obj->visitablePos())->visitableObjects.back()->tempOwner, &IGameEventsReceiver::showMarketWindow, market, hero, pack.queryID);
|
||||
}
|
||||
break;
|
||||
|
@ -31,10 +31,13 @@
|
||||
#include "gui/WindowHandler.h"
|
||||
#include "widgets/Buttons.h"
|
||||
#include "widgets/TextControls.h"
|
||||
#include "media/CMusicHandler.h"
|
||||
#include "media/IVideoPlayer.h"
|
||||
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../lib/serializer/Connection.h"
|
||||
#include "../lib/campaign/CampaignState.h"
|
||||
|
||||
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
|
||||
{
|
||||
@ -202,8 +205,19 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
|
||||
|
||||
if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
|
||||
{
|
||||
lobby->bonusSel = std::make_shared<CBonusSelection>();
|
||||
GH.windows().pushWindow(lobby->bonusSel);
|
||||
auto bonusSel = std::make_shared<CBonusSelection>();
|
||||
lobby->bonusSel = bonusSel;
|
||||
if(!handler.si->campState->conqueredScenarios().size() && !handler.si->campState->getIntroVideo().empty() && CCS->videoh->open(handler.si->campState->getIntroVideo(), false))
|
||||
{
|
||||
CCS->musich->stopMusic();
|
||||
GH.windows().createAndPushWindow<CampaignRimVideo>(handler.si->campState->getIntroVideo(), handler.si->campState->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getVideoRim(), [bonusSel](){
|
||||
if(!CSH->si->campState->getMusic().empty())
|
||||
CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false);
|
||||
GH.windows().pushWindow(bonusSel);
|
||||
});
|
||||
}
|
||||
else
|
||||
GH.windows().pushWindow(bonusSel);
|
||||
}
|
||||
|
||||
if(lobby->bonusSel)
|
||||
|
@ -15,11 +15,19 @@
|
||||
#include "../lib/CThreadHelper.h"
|
||||
#include "../server/CVCMIServer.h"
|
||||
|
||||
#ifndef VCMI_MOBILE
|
||||
#ifdef ENABLE_SERVER_PROCESS
|
||||
|
||||
#if BOOST_VERSION >= 108600
|
||||
// TODO: upgrade code to use v2 API instead of deprecated v1
|
||||
#include <boost/process/v1/child.hpp>
|
||||
#include <boost/process/v1/io.hpp>
|
||||
#else
|
||||
#include <boost/process/child.hpp>
|
||||
#include <boost/process/io.hpp>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#include <future>
|
||||
|
||||
ServerThreadRunner::ServerThreadRunner() = default;
|
||||
@ -66,7 +74,7 @@ int ServerThreadRunner::exitCode()
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef VCMI_MOBILE
|
||||
#ifdef ENABLE_SERVER_PROCESS
|
||||
|
||||
ServerProcessRunner::ServerProcessRunner() = default;
|
||||
ServerProcessRunner::~ServerProcessRunner() = default;
|
||||
|
@ -44,10 +44,23 @@ public:
|
||||
};
|
||||
|
||||
#ifndef VCMI_MOBILE
|
||||
// Enable support for running vcmiserver as separate process. Unavailable on mobile systems
|
||||
#define ENABLE_SERVER_PROCESS
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SERVER_PROCESS
|
||||
|
||||
#if BOOST_VERSION >= 108600
|
||||
namespace boost::process {
|
||||
inline namespace v1 {
|
||||
class child;
|
||||
}
|
||||
}
|
||||
#else
|
||||
namespace boost::process {
|
||||
class child;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Class that runs server instance as a child process
|
||||
/// Available only on desktop systems where process management is allowed
|
||||
|
@ -33,12 +33,13 @@
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/IImage.h"
|
||||
#include "../render/IRenderHandler.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
#include "../CMT.h"
|
||||
#include "../PlayerLocalState.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/StartInfo.h"
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/spells/CSpellHandler.h"
|
||||
@ -58,7 +59,7 @@ AdventureMapInterface::AdventureMapInterface():
|
||||
scrollingWasBlocked(false),
|
||||
backgroundDimLevel(settings["adventure"]["backgroundDimLevel"].Integer())
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos.x = pos.y = 0;
|
||||
pos.w = GH.screenDimensions().x;
|
||||
pos.h = GH.screenDimensions().y;
|
||||
@ -232,7 +233,7 @@ void AdventureMapInterface::handleMapScrollingUpdate(uint32_t timePassed)
|
||||
|
||||
bool cursorInScrollArea = scrollDelta != Point(0,0);
|
||||
bool scrollingActive = cursorInScrollArea && shortcuts->optionMapScrollingActive() && !scrollingWasBlocked;
|
||||
bool scrollingBlocked = GH.isKeyboardCtrlDown() || !settings["adventure"]["borderScroll"].Bool();
|
||||
bool scrollingBlocked = GH.isKeyboardCtrlDown() || !settings["adventure"]["borderScroll"].Bool() || !GH.screenHandler().hasFocus();
|
||||
|
||||
if (!scrollingWasActive && scrollingBlocked)
|
||||
{
|
||||
@ -375,7 +376,7 @@ void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID, bool isHuma
|
||||
mapAudio->onEnemyTurnStarted();
|
||||
widget->getMinimap()->setAIRadar(!isHuman);
|
||||
widget->getInfoBar()->startEnemyTurn(playerID);
|
||||
setState(isHuman ? EAdventureState::OTHER_HUMAN_PLAYER_TURN : EAdventureState::AI_PLAYER_TURN);
|
||||
setState(isHuman ? EAdventureState::MAKING_TURN : EAdventureState::AI_PLAYER_TURN);
|
||||
}
|
||||
|
||||
void AdventureMapInterface::setState(EAdventureState state)
|
||||
@ -452,7 +453,7 @@ void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
|
||||
widget->getInfoBar()->showDate();
|
||||
|
||||
onHeroChanged(nullptr);
|
||||
Canvas canvas = Canvas::createFromSurface(screen);
|
||||
Canvas canvas = Canvas::createFromSurface(screen, CanvasScalingPolicy::AUTO);
|
||||
showAll(canvas);
|
||||
mapAudio->onPlayerTurnStarted();
|
||||
|
||||
@ -616,7 +617,7 @@ void AdventureMapInterface::onTileHovered(const int3 &targetPosition)
|
||||
case SpellID::DIMENSION_DOOR:
|
||||
if(isValidAdventureSpellTarget(targetPosition))
|
||||
{
|
||||
if(VLC->settings()->getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && LOCPLINT->cb->isTileGuardedUnchecked(targetPosition))
|
||||
if(LOCPLINT->cb->getSettings().getBoolean(EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS) && LOCPLINT->cb->isTileGuardedUnchecked(targetPosition))
|
||||
CCS->curh->set(Cursor::Map::T1_ATTACK);
|
||||
else
|
||||
CCS->curh->set(Cursor::Map::TELEPORT);
|
||||
@ -898,7 +899,7 @@ void AdventureMapInterface::hotkeyZoom(int delta, bool useDeadZone)
|
||||
|
||||
void AdventureMapInterface::onScreenResize()
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
// remember our activation state and reactive after reconstruction
|
||||
// since othervice activate() calls for created elements will bypass virtual dispatch
|
||||
|
@ -325,7 +325,6 @@ void AdventureMapShortcuts::toMainMenu()
|
||||
[]()
|
||||
{
|
||||
CSH->endGameplay();
|
||||
GH.defActionsDef = 63;
|
||||
CMM->menu->switchToTab("main");
|
||||
},
|
||||
0
|
||||
@ -339,7 +338,6 @@ void AdventureMapShortcuts::newGame()
|
||||
[]()
|
||||
{
|
||||
CSH->endGameplay();
|
||||
GH.defActionsDef = 63;
|
||||
CMM->menu->switchToTab("new");
|
||||
},
|
||||
nullptr
|
||||
@ -532,7 +530,6 @@ bool AdventureMapShortcuts::optionCanVisitObject()
|
||||
auto * hero = LOCPLINT->localState->getCurrentHero();
|
||||
auto objects = LOCPLINT->cb->getVisitableObjs(hero->visitablePos());
|
||||
|
||||
assert(vstd::contains(objects,hero));
|
||||
return objects.size() > 1; // there is object other than our hero
|
||||
}
|
||||
|
||||
@ -577,16 +574,15 @@ bool AdventureMapShortcuts::optionInWorldView()
|
||||
|
||||
bool AdventureMapShortcuts::optionSidePanelActive()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionMapScrollingActive()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW;
|
||||
}
|
||||
|
||||
bool AdventureMapShortcuts::optionMapViewActive()
|
||||
{
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL
|
||||
|| state == EAdventureState::OTHER_HUMAN_PLAYER_TURN;
|
||||
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL;
|
||||
}
|
||||
|
@ -381,7 +381,7 @@ CAdventureMapIcon::CAdventureMapIcon(const Point & position, const AnimationPath
|
||||
: index(index)
|
||||
, iconsPerPlayer(iconsPerPlayer)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos += position;
|
||||
image = std::make_shared<CAnimImage>(animation, index);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
AdventureOptions::AdventureOptions()
|
||||
: CWindowObject(PLAYER_COLORED, ImagePath::builtin("ADVOPTS"))
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
viewWorld = std::make_shared<CButton>(Point(24, 23), AnimationPath::builtin("ADVVIEW.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
|
||||
viewWorld->addCallback([] { LOCPLINT->viewWorldMap(); });
|
||||
|
@ -15,7 +15,6 @@ enum class EAdventureState
|
||||
HOTSEAT_WAIT,
|
||||
MAKING_TURN,
|
||||
AI_PLAYER_TURN,
|
||||
OTHER_HUMAN_PLAYER_TURN,
|
||||
CASTING_SPELL,
|
||||
WORLD_VIEW
|
||||
};
|
||||
|
@ -51,7 +51,7 @@ CInfoBar::EmptyVisibleInfo::EmptyVisibleInfo()
|
||||
|
||||
CInfoBar::VisibleHeroInfo::VisibleHeroInfo(const CGHeroInstance * hero)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATHR"));
|
||||
|
||||
if(settings["gameTweaks"]["infoBarCreatureManagement"].Bool())
|
||||
@ -62,7 +62,7 @@ CInfoBar::VisibleHeroInfo::VisibleHeroInfo(const CGHeroInstance * hero)
|
||||
|
||||
CInfoBar::VisibleTownInfo::VisibleTownInfo(const CGTownInstance * town)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATCS"));
|
||||
|
||||
if(settings["gameTweaks"]["infoBarCreatureManagement"].Bool())
|
||||
@ -73,7 +73,7 @@ CInfoBar::VisibleTownInfo::VisibleTownInfo(const CGTownInstance * town)
|
||||
|
||||
CInfoBar::VisibleDateInfo::VisibleDateInfo()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
animation = std::make_shared<CShowableAnim>(1, 0, getNewDayName(), CShowableAnim::PLAY_ONCE, 180);// H3 uses around 175-180 ms per frame
|
||||
animation->setDuration(1500);
|
||||
@ -114,7 +114,7 @@ AnimationPath CInfoBar::VisibleDateInfo::getNewDayName()
|
||||
|
||||
CInfoBar::VisibleEnemyTurnInfo::VisibleEnemyTurnInfo(PlayerColor player)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATNX"));
|
||||
banner = std::make_shared<CAnimImage>(AnimationPath::builtin("CREST58"), player.getNum(), 0, 20, 51);
|
||||
sand = std::make_shared<CShowableAnim>(99, 51, AnimationPath::builtin("HOURSAND"), 0, 100); // H3 uses around 100 ms per frame
|
||||
@ -123,7 +123,7 @@ CInfoBar::VisibleEnemyTurnInfo::VisibleEnemyTurnInfo(PlayerColor player)
|
||||
|
||||
CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
//get amount of halls of each level
|
||||
std::vector<int> halls(4, 0);
|
||||
for(auto town : LOCPLINT->localState->getOwnedTowns())
|
||||
@ -180,7 +180,7 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
|
||||
|
||||
CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector<Component> & compsToDisplay, std::string message, int textH, bool tiny)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATOT"), 1, 0);
|
||||
auto fullRect = Rect(CInfoBar::offset, CInfoBar::offset, data_width - 2 * CInfoBar::offset, data_height - 2 * CInfoBar::offset);
|
||||
@ -250,14 +250,14 @@ void CInfoBar::playNewDaySound()
|
||||
|
||||
void CInfoBar::reset()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
state = EMPTY;
|
||||
visibleInfo = std::make_shared<EmptyVisibleInfo>();
|
||||
}
|
||||
|
||||
void CInfoBar::showSelection()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
if(LOCPLINT->localState->getCurrentHero())
|
||||
{
|
||||
showHeroSelection(LOCPLINT->localState->getCurrentHero());
|
||||
@ -325,7 +325,7 @@ CInfoBar::CInfoBar(const Rect & position)
|
||||
state(EMPTY),
|
||||
listener(settings.listen["gameTweaks"]["infoBarCreatureManagement"])
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
pos.w = position.w;
|
||||
pos.h = position.h;
|
||||
listener(std::bind(&CInfoBar::OnInfoBarCreatureManagementChanged, this));
|
||||
@ -349,7 +349,7 @@ void CInfoBar::setTimer(uint32_t msToTrigger)
|
||||
|
||||
void CInfoBar::showDate()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
playNewDaySound();
|
||||
state = DATE;
|
||||
visibleInfo = std::make_shared<VisibleDateInfo>();
|
||||
@ -475,7 +475,7 @@ void CInfoBar::popAll()
|
||||
|
||||
void CInfoBar::popComponents(bool remove)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
if(remove && !componentsQueue.empty())
|
||||
componentsQueue.pop();
|
||||
if(!componentsQueue.empty())
|
||||
@ -492,7 +492,7 @@ void CInfoBar::popComponents(bool remove)
|
||||
|
||||
void CInfoBar::pushComponents(const std::vector<Component> & comps, std::string message, int textH, bool tiny, int timer)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
componentsQueue.emplace(VisibleComponentInfo::Cache(comps, message, textH, tiny), timer);
|
||||
}
|
||||
|
||||
@ -503,7 +503,7 @@ bool CInfoBar::showingComponents()
|
||||
|
||||
void CInfoBar::startEnemyTurn(PlayerColor color)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
state = AITURN;
|
||||
visibleInfo = std::make_shared<VisibleEnemyTurnInfo>(color);
|
||||
redraw();
|
||||
@ -511,7 +511,7 @@ void CInfoBar::startEnemyTurn(PlayerColor color)
|
||||
|
||||
void CInfoBar::showHeroSelection(const CGHeroInstance * hero)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
if(!hero)
|
||||
{
|
||||
reset();
|
||||
@ -526,7 +526,7 @@ void CInfoBar::showHeroSelection(const CGHeroInstance * hero)
|
||||
|
||||
void CInfoBar::showTownSelection(const CGTownInstance * town)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
if(!town)
|
||||
{
|
||||
reset();
|
||||
@ -541,7 +541,7 @@ void CInfoBar::showTownSelection(const CGTownInstance * town)
|
||||
|
||||
void CInfoBar::showGameStatus()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
state = GAME;
|
||||
visibleInfo = std::make_shared<VisibleGameStatusInfo>();
|
||||
setTimer(3000);
|
||||
|
@ -18,26 +18,29 @@
|
||||
#include "../widgets/ObjectLists.h"
|
||||
#include "../widgets/RadialMenu.h"
|
||||
#include "../windows/InfoWindows.h"
|
||||
#include "../windows/CCastleInterface.h"
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
#include "../PlayerLocalState.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../gui/Shortcut.h"
|
||||
#include "../gui/WindowHandler.h"
|
||||
#include "../render/Canvas.h"
|
||||
#include "../render/Colors.h"
|
||||
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/GameSettings.h"
|
||||
#include "../../lib/IGameSettings.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
CList::CListItem::CListItem(CList * Parent)
|
||||
: CIntObject(LCLICK | SHOW_POPUP | HOVER),
|
||||
parent(Parent),
|
||||
selection()
|
||||
{
|
||||
defActions = 255-DISPOSE;
|
||||
}
|
||||
|
||||
CList::CListItem::~CListItem() = default;
|
||||
@ -71,7 +74,7 @@ void CList::CListItem::hover(bool on)
|
||||
|
||||
void CList::CListItem::onSelect(bool on)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
selection.reset();
|
||||
if(on)
|
||||
selection = genSelection();
|
||||
@ -96,7 +99,7 @@ void CList::showAll(Canvas & to)
|
||||
|
||||
void CList::createList(Point firstItemPosition, Point itemPositionDelta, size_t listAmount)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
listBox = std::make_shared<CListBox>(std::bind(&CList::createItem, this, _1), firstItemPosition, itemPositionDelta, size, listAmount);
|
||||
}
|
||||
|
||||
@ -207,7 +210,7 @@ void CList::selectPrev()
|
||||
|
||||
CHeroList::CEmptyHeroItem::CEmptyHeroItem()
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
|
||||
portrait = std::make_shared<CPicture>(ImagePath::builtin("HPSXXX"), movement->pos.w + 1, 0);
|
||||
mana = std::make_shared<CAnimImage>(AnimationPath::builtin("IMANA"), 0, 0, movement->pos.w + portrait->pos.w + 2, 1 );
|
||||
@ -220,7 +223,7 @@ CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
|
||||
: CListItem(parent),
|
||||
hero(Hero)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
|
||||
portrait = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), hero->getIconIndex(), 0, movement->pos.w + 1);
|
||||
mana = std::make_shared<CAnimImage>(AnimationPath::builtin("IMANA"), 0, 0, movement->pos.w + portrait->pos.w + 2, 1);
|
||||
@ -230,7 +233,7 @@ CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
|
||||
|
||||
update();
|
||||
|
||||
addUsedEvents(GESTURE);
|
||||
addUsedEvents(GESTURE | KEYBOARD);
|
||||
}
|
||||
|
||||
void CHeroList::CHeroItem::update()
|
||||
@ -301,6 +304,55 @@ void CHeroList::CHeroItem::gesture(bool on, const Point & initialPosition, const
|
||||
GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements, true);
|
||||
}
|
||||
|
||||
void CHeroList::CHeroItem::keyPressed(EShortcut key)
|
||||
{
|
||||
if(!hero)
|
||||
return;
|
||||
|
||||
if(parent->selected != this->shared_from_this())
|
||||
return;
|
||||
|
||||
auto & heroes = LOCPLINT->localState->getWanderingHeroes();
|
||||
|
||||
if(key == EShortcut::LIST_HERO_DISMISS)
|
||||
{
|
||||
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], [=](){ LOCPLINT->cb->dismissHero(hero); }, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if(heroes.size() < 2)
|
||||
return;
|
||||
|
||||
size_t heroPos = vstd::find_pos(heroes, hero);
|
||||
const CGHeroInstance * heroUpper = (heroPos < 1) ? nullptr : heroes.at(heroPos - 1);
|
||||
const CGHeroInstance * heroLower = (heroPos > heroes.size() - 2) ? nullptr : heroes.at(heroPos + 1);
|
||||
|
||||
switch(key)
|
||||
{
|
||||
case EShortcut::LIST_HERO_UP:
|
||||
if(heroUpper)
|
||||
LOCPLINT->localState->swapWanderingHero(heroPos, heroPos - 1);
|
||||
break;
|
||||
|
||||
case EShortcut::LIST_HERO_DOWN:
|
||||
if(heroLower)
|
||||
LOCPLINT->localState->swapWanderingHero(heroPos, heroPos + 1);
|
||||
break;
|
||||
|
||||
case EShortcut::LIST_HERO_TOP:
|
||||
if(heroUpper)
|
||||
for (size_t i = heroPos; i > 0; i--)
|
||||
LOCPLINT->localState->swapWanderingHero(i, i - 1);
|
||||
break;
|
||||
|
||||
case EShortcut::LIST_HERO_BOTTOM:
|
||||
if(heroLower)
|
||||
for (int i = heroPos; i < heroes.size() - 1; i++)
|
||||
LOCPLINT->localState->swapWanderingHero(i, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<CIntObject> CHeroList::createItem(size_t index)
|
||||
{
|
||||
if (LOCPLINT->localState->getWanderingHeroes().size() > index)
|
||||
@ -365,12 +417,12 @@ CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
|
||||
CListItem(parent),
|
||||
town(Town)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 0);
|
||||
pos = picture->pos;
|
||||
update();
|
||||
|
||||
addUsedEvents(GESTURE);
|
||||
addUsedEvents(GESTURE | KEYBOARD);
|
||||
}
|
||||
|
||||
std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
|
||||
@ -380,7 +432,7 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
|
||||
|
||||
void CTownList::CTownItem::update()
|
||||
{
|
||||
size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
|
||||
size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
|
||||
|
||||
picture->setFrame(iconIndex + 2);
|
||||
redraw();
|
||||
@ -419,24 +471,77 @@ void CTownList::CTownItem::gesture(bool on, const Point & initialPosition, const
|
||||
int townUpperPos = (townIndex < 1) ? -1 : townIndex - 1;
|
||||
int townLowerPos = (townIndex > towns.size() - 2) ? -1 : townIndex + 1;
|
||||
|
||||
auto updateList = [](){
|
||||
for (auto ki : GH.windows().findWindows<CCastleInterface>())
|
||||
ki->townChange(); //update list
|
||||
};
|
||||
|
||||
std::vector<RadialMenuConfig> menuElements = {
|
||||
{ RadialMenuConfig::ITEM_ALT_NN, townUpperPos > -1, "altUpTop", "vcmi.radialWheel.moveTop", [townIndex]()
|
||||
{ RadialMenuConfig::ITEM_ALT_NN, townUpperPos > -1, "altUpTop", "vcmi.radialWheel.moveTop", [updateList, townIndex]()
|
||||
{
|
||||
for (int i = townIndex; i > 0; i--)
|
||||
LOCPLINT->localState->swapOwnedTowns(i, i - 1);
|
||||
updateList();
|
||||
} },
|
||||
{ RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [townIndex, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); } },
|
||||
{ RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [townIndex, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); } },
|
||||
{ RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [townIndex, towns]()
|
||||
{ RadialMenuConfig::ITEM_ALT_NW, townUpperPos > -1, "altUp", "vcmi.radialWheel.moveUp", [updateList, townIndex, townUpperPos](){LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos); updateList(); } },
|
||||
{ RadialMenuConfig::ITEM_ALT_SW, townLowerPos > -1, "altDown", "vcmi.radialWheel.moveDown", [updateList, townIndex, townLowerPos](){ LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos); updateList(); } },
|
||||
{ RadialMenuConfig::ITEM_ALT_SS, townLowerPos > -1, "altDownBottom", "vcmi.radialWheel.moveBottom", [updateList, townIndex, towns]()
|
||||
{
|
||||
for (int i = townIndex; i < towns.size() - 1; i++)
|
||||
LOCPLINT->localState->swapOwnedTowns(i, i + 1);
|
||||
updateList();
|
||||
} },
|
||||
};
|
||||
|
||||
GH.windows().createAndPushWindow<RadialMenu>(pos.center(), menuElements, true);
|
||||
}
|
||||
|
||||
void CTownList::CTownItem::keyPressed(EShortcut key)
|
||||
{
|
||||
if(parent->selected != this->shared_from_this())
|
||||
return;
|
||||
|
||||
const std::vector<const CGTownInstance *> towns = LOCPLINT->localState->getOwnedTowns();
|
||||
size_t townIndex = vstd::find_pos(towns, town);
|
||||
|
||||
if(townIndex + 1 > towns.size() || !towns.at(townIndex))
|
||||
return;
|
||||
|
||||
if(towns.size() < 2)
|
||||
return;
|
||||
|
||||
int townUpperPos = (townIndex < 1) ? -1 : townIndex - 1;
|
||||
int townLowerPos = (townIndex > towns.size() - 2) ? -1 : townIndex + 1;
|
||||
|
||||
switch(key)
|
||||
{
|
||||
case EShortcut::LIST_TOWN_UP:
|
||||
if(townUpperPos > -1)
|
||||
LOCPLINT->localState->swapOwnedTowns(townIndex, townUpperPos);
|
||||
break;
|
||||
|
||||
case EShortcut::LIST_TOWN_DOWN:
|
||||
if(townLowerPos > -1)
|
||||
LOCPLINT->localState->swapOwnedTowns(townIndex, townLowerPos);
|
||||
break;
|
||||
|
||||
case EShortcut::LIST_TOWN_TOP:
|
||||
if(townUpperPos > -1)
|
||||
for (int i = townIndex; i > 0; i--)
|
||||
LOCPLINT->localState->swapOwnedTowns(i, i - 1);
|
||||
break;
|
||||
|
||||
case EShortcut::LIST_TOWN_BOTTOM:
|
||||
if(townLowerPos > -1)
|
||||
for (int i = townIndex; i < towns.size() - 1; i++)
|
||||
LOCPLINT->localState->swapOwnedTowns(i, i + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto ki : GH.windows().findWindows<CCastleInterface>())
|
||||
ki->townChange(); //update list
|
||||
}
|
||||
|
||||
std::string CTownList::CTownItem::getHoverText()
|
||||
{
|
||||
return town->getObjectName();
|
||||
|
@ -29,9 +29,10 @@ class CList : public Scrollable
|
||||
protected:
|
||||
class CListItem : public CIntObject, public std::enable_shared_from_this<CListItem>
|
||||
{
|
||||
CList * parent;
|
||||
std::shared_ptr<CIntObject> selection;
|
||||
public:
|
||||
CList * parent;
|
||||
|
||||
CListItem(CList * parent);
|
||||
~CListItem();
|
||||
|
||||
@ -55,9 +56,6 @@ protected:
|
||||
|
||||
private:
|
||||
const size_t size;
|
||||
|
||||
//for selection\deselection
|
||||
std::shared_ptr<CListItem> selected;
|
||||
void select(std::shared_ptr<CListItem> which);
|
||||
friend class CListItem;
|
||||
|
||||
@ -81,6 +79,9 @@ protected:
|
||||
void update();
|
||||
|
||||
public:
|
||||
//for selection\deselection
|
||||
std::shared_ptr<CListItem> selected;
|
||||
|
||||
/// functions that will be called when selection changes
|
||||
CFunctionList<void()> onSelect;
|
||||
|
||||
@ -128,6 +129,7 @@ class CHeroList : public CList
|
||||
void open() override;
|
||||
void showTooltip() override;
|
||||
void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
|
||||
void keyPressed(EShortcut key) override;
|
||||
std::string getHoverText() override;
|
||||
};
|
||||
|
||||
@ -162,6 +164,7 @@ class CTownList : public CList
|
||||
void open() override;
|
||||
void showTooltip() override;
|
||||
void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
|
||||
void keyPressed(EShortcut key) override;
|
||||
std::string getHoverText() override;
|
||||
};
|
||||
|
||||
|
@ -73,7 +73,7 @@ void CMinimapInstance::redrawMinimap()
|
||||
|
||||
CMinimapInstance::CMinimapInstance(CMinimap *Parent, int Level):
|
||||
parent(Parent),
|
||||
minimap(new Canvas(Point(LOCPLINT->cb->getMapSize().x, LOCPLINT->cb->getMapSize().y))),
|
||||
minimap(new Canvas(Point(LOCPLINT->cb->getMapSize().x, LOCPLINT->cb->getMapSize().y), CanvasScalingPolicy::IGNORE)),
|
||||
level(Level)
|
||||
{
|
||||
pos.w = parent->pos.w;
|
||||
@ -90,7 +90,7 @@ CMinimap::CMinimap(const Rect & position)
|
||||
: CIntObject(LCLICK | SHOW_POPUP | DRAG | MOVE | GESTURE, position.topLeft()),
|
||||
level(0)
|
||||
{
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
double maxSideLengthSrc = std::max(LOCPLINT->cb->getMapSize().x, LOCPLINT->cb->getMapSize().y);
|
||||
double maxSideLengthDst = std::max(position.w, position.h);
|
||||
@ -202,7 +202,7 @@ void CMinimap::update()
|
||||
if(aiShield->recActions & UPDATE) //AI turn is going on. There is no need to update minimap
|
||||
return;
|
||||
|
||||
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
minimap = std::make_shared<CMinimapInstance>(this, level);
|
||||
redraw();
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ CResDataBar::CResDataBar(const ImagePath & imageName, const Point & position)
|
||||
pos.x += position.x;
|
||||
pos.y += position.y;
|
||||
|
||||
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
|
||||
OBJECT_CONSTRUCTION;
|
||||
background = std::make_shared<CPicture>(imageName, 0, 0);
|
||||
background->setPlayerColor(LOCPLINT->playerID);
|
||||
|
||||
|
@ -35,7 +35,7 @@ TurnTimerWidget::TurnTimerWidget(const Point & position, PlayerColor player)
|
||||
, lastSoundCheckSeconds(0)
|
||||
, isBattleMode(player.isValidPlayer())
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
pos += position;
|
||||
pos.w = 0;
|
||||
|
@ -312,8 +312,8 @@ void BattleActionsController::castThisSpell(SpellID spellID)
|
||||
heroSpellToCast = std::make_shared<BattleAction>();
|
||||
heroSpellToCast->actionType = EActionType::HERO_SPELL;
|
||||
heroSpellToCast->spell = spellID;
|
||||
heroSpellToCast->stackNumber = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? -1 : -2;
|
||||
heroSpellToCast->side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false;
|
||||
heroSpellToCast->stackNumber = -1;
|
||||
heroSpellToCast->side = owner.curInt->cb->getBattle(owner.getBattleID())->battleGetMySide();
|
||||
|
||||
//choosing possible targets
|
||||
const CGHeroInstance *castingHero = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? owner.attackingHeroInstance : owner.defendingHeroInstance;
|
||||
@ -499,9 +499,12 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
|
||||
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
|
||||
{
|
||||
const auto * attacker = owner.stacksController->getActiveStack();
|
||||
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
|
||||
int distance = attacker->position.isValid() ? owner.getBattle()->battleGetDistances(attacker, attacker->getPosition())[attackFromHex] : 0;
|
||||
DamageEstimation retaliation;
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex, &retaliation);
|
||||
BattleAttackInfo attackInfo(attacker, targetStack, distance, false );
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
bool enemyMayBeKilled = estimation.kills.max == targetStack->getCount();
|
||||
@ -514,7 +517,8 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
|
||||
const auto * shooter = owner.stacksController->getActiveStack();
|
||||
|
||||
DamageEstimation retaliation;
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(shooter, targetStack, shooter->getPosition(), &retaliation);
|
||||
BattleAttackInfo attackInfo(shooter, targetStack, 0, true );
|
||||
DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(attackInfo, &retaliation);
|
||||
estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
|
||||
estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
|
||||
return formatRangedAttack(estimation, targetStack->getName(), shooter->shots.available());
|
||||
|
@ -161,7 +161,7 @@ ECreatureAnimType AttackAnimation::findValidGroup( const std::vector<ECreatureAn
|
||||
const CCreature * AttackAnimation::getCreature() const
|
||||
{
|
||||
if (attackingStack->unitType()->getId() == CreatureID::ARROW_TOWERS)
|
||||
return owner.siegeController->getTurretCreature();
|
||||
return owner.siegeController->getTurretCreature(attackingStack->initialPosition);
|
||||
else
|
||||
return attackingStack->unitType();
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ static const std::map<int, int> hexEdgeMaskToFrameIndex =
|
||||
BattleFieldController::BattleFieldController(BattleInterface & owner):
|
||||
owner(owner)
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
//preparing cells and hexes
|
||||
cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY);
|
||||
@ -124,6 +124,8 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
|
||||
rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"), EImageBlitMode::COLORKEY);
|
||||
shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"), EImageBlitMode::COLORKEY);
|
||||
|
||||
cellShade->setShadowEnabled(true);
|
||||
|
||||
if(!owner.siegeController)
|
||||
{
|
||||
auto bfieldType = owner.getBattle()->battleGetBattlefieldType();
|
||||
@ -142,7 +144,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
|
||||
pos.w = background->width();
|
||||
pos.h = background->height();
|
||||
|
||||
backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()));
|
||||
backgroundWithHexes = std::make_unique<Canvas>(Point(background->width(), background->height()), CanvasScalingPolicy::AUTO);
|
||||
|
||||
updateAccessibleHexes();
|
||||
addUsedEvents(LCLICK | SHOW_POPUP | MOVE | TIME | GESTURE);
|
||||
@ -156,7 +158,7 @@ void BattleFieldController::activate()
|
||||
|
||||
void BattleFieldController::createHeroes()
|
||||
{
|
||||
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
// create heroes as part of our constructor for correct positioning inside battlefield
|
||||
if(owner.attackingHeroInstance)
|
||||
|
@ -85,7 +85,7 @@ BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet *
|
||||
this->army2 = army2;
|
||||
|
||||
const CGTownInstance *town = getBattle()->battleGetDefendedTown();
|
||||
if(town && town->hasFort())
|
||||
if(town && town->fortificationsLevel().wallsHealth > 0)
|
||||
siegeController.reset(new BattleSiegeController(*this, town));
|
||||
|
||||
windowObject = std::make_shared<BattleWindow>(*this);
|
||||
@ -229,19 +229,19 @@ void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedI
|
||||
{
|
||||
stacksController->stacksAreAttacked(attackedInfos);
|
||||
|
||||
std::array<int, 2> killedBySide = {0, 0};
|
||||
BattleSideArray<int> killedBySide;
|
||||
|
||||
for(const StackAttackedInfo & attackedInfo : attackedInfos)
|
||||
{
|
||||
ui8 side = attackedInfo.defender->unitSide();
|
||||
BattleSide side = attackedInfo.defender->unitSide();
|
||||
killedBySide.at(side) += attackedInfo.amountKilled;
|
||||
}
|
||||
|
||||
for(ui8 side = 0; side < 2; side++)
|
||||
for(BattleSide side : { BattleSide::ATTACKER, BattleSide::DEFENDER })
|
||||
{
|
||||
if(killedBySide.at(side) > killedBySide.at(1-side))
|
||||
if(killedBySide.at(side) > killedBySide.at(getBattle()->otherSide(side)))
|
||||
setHeroAnimation(side, EHeroAnimType::DEFEAT);
|
||||
else if(killedBySide.at(side) < killedBySide.at(1-side))
|
||||
else if(killedBySide.at(side) < killedBySide.at(getBattle()->otherSide(side)))
|
||||
setHeroAnimation(side, EHeroAnimType::VICTORY);
|
||||
}
|
||||
}
|
||||
@ -271,14 +271,14 @@ void BattleInterface::giveCommand(EActionType action, BattleHex tile, SpellID sp
|
||||
}
|
||||
|
||||
auto side = getBattle()->playerToSide(curInt->playerID);
|
||||
if(!side)
|
||||
if(side == BattleSide::NONE)
|
||||
{
|
||||
logGlobal->error("Player %s is not in battle", curInt->playerID.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
BattleAction ba;
|
||||
ba.side = side.value();
|
||||
ba.side = side;
|
||||
ba.actionType = action;
|
||||
ba.aimToHex(tile);
|
||||
ba.spell = spell;
|
||||
@ -409,7 +409,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
}
|
||||
else
|
||||
{
|
||||
auto hero = sc->side ? defendingHero : attackingHero;
|
||||
auto hero = sc->side == BattleSide::DEFENDER ? defendingHero : attackingHero;
|
||||
assert(hero);
|
||||
|
||||
addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]()
|
||||
@ -466,11 +466,11 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
|
||||
{
|
||||
Point leftHero = Point(15, 30);
|
||||
Point rightHero = Point(755, 30);
|
||||
bool side = sc->side;
|
||||
BattleSide side = sc->side;
|
||||
|
||||
addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side ? "SP07_A.DEF" : "SP07_B.DEF"), leftHero));
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side ? "SP07_B.DEF" : "SP07_A.DEF"), rightHero));
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side == BattleSide::DEFENDER ? "SP07_A.DEF" : "SP07_B.DEF"), leftHero));
|
||||
stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side == BattleSide::DEFENDER ? "SP07_B.DEF" : "SP07_A.DEF"), rightHero));
|
||||
});
|
||||
}
|
||||
|
||||
@ -483,7 +483,7 @@ void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
|
||||
fieldController->redrawBackgroundWithHexes();
|
||||
}
|
||||
|
||||
void BattleInterface::setHeroAnimation(ui8 side, EHeroAnimType phase)
|
||||
void BattleInterface::setHeroAnimation(BattleSide side, EHeroAnimType phase)
|
||||
{
|
||||
if(side == BattleSide::ATTACKER)
|
||||
{
|
||||
@ -656,7 +656,7 @@ void BattleInterface::tacticPhaseEnd()
|
||||
tacticsMode = false;
|
||||
|
||||
auto side = tacticianInterface->cb->getBattle(battleID)->playerToSide(tacticianInterface->playerID);
|
||||
auto action = BattleAction::makeEndOFTacticPhase(*side);
|
||||
auto action = BattleAction::makeEndOFTacticPhase(side);
|
||||
|
||||
tacticianInterface->cb->battleMakeTacticAction(battleID, action);
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ public:
|
||||
|
||||
void showInterface(Canvas & to);
|
||||
|
||||
void setHeroAnimation(ui8 side, EHeroAnimType phase);
|
||||
void setHeroAnimation(BattleSide side, EHeroAnimType phase);
|
||||
|
||||
void executeSpellCast(); //called when a hero casts a spell
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user