mirror of
https://github.com/vcmi/vcmi.git
synced 2025-04-11 11:31:52 +02:00
Merge branch 'develop' into split-client+develop
This commit is contained in:
commit
e9131538dd
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'
|
||||
|
@ -201,6 +201,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)
|
||||
@ -208,9 +210,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;
|
||||
@ -220,9 +226,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;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#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
|
||||
@ -122,6 +123,11 @@ static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, BattleSid
|
||||
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());
|
||||
@ -154,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);
|
||||
|
||||
|
@ -49,6 +49,45 @@ 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);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
|
||||
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);
|
||||
cachedScore = EvaluationResult::INEFFECTIVE_SCORE;
|
||||
}
|
||||
|
||||
std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
|
||||
{
|
||||
std::vector<BattleHex> result;
|
||||
@ -167,7 +206,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
score
|
||||
);
|
||||
|
||||
if (moveTarget.scorePerTurn <= score)
|
||||
if (moveTarget.score <= score)
|
||||
{
|
||||
if(evaluationResult.wait)
|
||||
{
|
||||
@ -197,7 +236,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
}
|
||||
|
||||
//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;
|
||||
@ -206,14 +245,13 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
|
||||
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
|
||||
{
|
||||
@ -235,7 +273,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 +287,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 +324,38 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
|
||||
|
||||
std::vector<BattleHex> targetHexes = hexes;
|
||||
|
||||
for(int i = 0; i < 5; i++)
|
||||
{
|
||||
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||
{
|
||||
return reachability.distances[h1] < reachability.distances[h2];
|
||||
});
|
||||
vstd::erase_if(targetHexes, [](const BattleHex & hex) { return !hex.isValid(); });
|
||||
|
||||
for(auto hex : targetHexes)
|
||||
std::sort(targetHexes.begin(), targetHexes.end(), [&](BattleHex h1, BattleHex h2) -> bool
|
||||
{
|
||||
if(vstd::contains(avHexes, hex))
|
||||
{
|
||||
return BattleAction::makeMove(stack, hex);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
logAi->warn("Warning: already standing on neighbouring tile!");
|
||||
//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);
|
||||
}
|
||||
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 moveOrAttack(stack, hex, targets);
|
||||
}
|
||||
|
||||
if(stack->coversPos(hex))
|
||||
{
|
||||
logAi->warn("Warning: already standing on neighbouring hex!");
|
||||
//We shouldn't even be here...
|
||||
return BattleAction::makeDefend(stack);
|
||||
}
|
||||
}
|
||||
|
||||
// not this turn
|
||||
scoreEvaluator.updateReachabilityMap(hb);
|
||||
|
||||
if(stack->hasBonusOfType(BonusType::FLYING))
|
||||
@ -343,7 +395,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 +409,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()
|
||||
@ -600,7 +657,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
|
||||
#endif
|
||||
|
||||
PotentialTargets innerTargets(activeStack, innerCache, state);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio);
|
||||
BattleExchangeEvaluator innerEvaluator(state, env, strengthRatio, simulationTurnsCount);
|
||||
|
||||
if(!innerTargets.possibleAttacks.empty())
|
||||
{
|
||||
|
@ -37,16 +37,18 @@ class BattleEvaluator
|
||||
float cachedScore;
|
||||
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,
|
||||
@ -55,15 +57,8 @@ public:
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide 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;
|
||||
}
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount);
|
||||
|
||||
BattleEvaluator(
|
||||
std::shared_ptr<Environment> env,
|
||||
@ -74,10 +69,6 @@ public:
|
||||
PlayerColor playerID,
|
||||
BattleID battleID,
|
||||
BattleSide 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@ -42,7 +42,7 @@ float BattleExchangeVariant::trackAttack(
|
||||
for(auto affectedUnit : affectedUnits)
|
||||
{
|
||||
auto unitToUpdate = hb->getForUpdate(affectedUnit->unitId());
|
||||
auto damageDealt = unitToUpdate->getTotalHealth() - affectedUnit->getTotalHealth();
|
||||
auto damageDealt = unitToUpdate->getAvailableHealth() - affectedUnit->getAvailableHealth();
|
||||
|
||||
if(damageDealt > 0)
|
||||
{
|
||||
@ -58,7 +58,7 @@ float BattleExchangeVariant::trackAttack(
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace(
|
||||
"%s -> %s, ap retaliation, %s, dps: %lld",
|
||||
ap.attack.defender->getDescription(),
|
||||
hb->getForUpdate(ap.attack.defender->unitId())->getDescription(),
|
||||
ap.attack.attacker->getDescription(),
|
||||
ap.attack.shooting ? "shot" : "mellee",
|
||||
damageDealt);
|
||||
@ -277,6 +277,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() / dmg;
|
||||
|
||||
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,
|
||||
@ -286,6 +316,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;
|
||||
|
||||
@ -296,17 +328,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;
|
||||
@ -315,30 +347,84 @@ 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(BattleHex enemyHex : enemy->getAttackableHexes(activeStack))
|
||||
{
|
||||
while(!flying && dists.distances[enemyHex] > speed)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -382,7 +468,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;
|
||||
|
||||
@ -390,13 +477,26 @@ 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));
|
||||
}
|
||||
|
||||
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;
|
||||
@ -432,7 +532,7 @@ ReachabilityData BattleExchangeEvaluator::getExchangeUnits(
|
||||
|
||||
for(auto unit : allReachableUnits)
|
||||
{
|
||||
auto accessible = !unit->canShoot();
|
||||
auto accessible = !unit->canShoot() || vstd::contains(additionalUnits, unit);
|
||||
|
||||
if(!accessible)
|
||||
{
|
||||
@ -456,14 +556,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
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
vstd::erase_if(result.units[turn], [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !hb->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -494,7 +594,8 @@ 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);
|
||||
@ -513,7 +614,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())
|
||||
{
|
||||
@ -523,22 +624,25 @@ 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++)
|
||||
{
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
for(auto unit : exchangeUnits.units.at(exchangeTurn))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
if(unit->isTurret())
|
||||
continue;
|
||||
|
||||
bool isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, unit, true);
|
||||
auto & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
auto u = exchangeBattle->getForUpdate(unit->unitId());
|
||||
|
||||
if(u->alive() && !vstd::contains(attackerQueue, unit))
|
||||
{
|
||||
attackerQueue.push_back(unit);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
logAi->trace("Exchanging: %s", u->getDescription());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,122 +656,166 @@ 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 isOur = exchangeBattle->battleMatchOwner(ap.attack.attacker, activeUnit, true);
|
||||
battle::Units & attackerQueue = isOur ? ourStacks : enemyStacks;
|
||||
battle::Units & oppositeQueue = isOur ? enemyStacks : ourStacks;
|
||||
bool isMovingTurm = exchangeTurn < turn;
|
||||
int queueTurn = exchangeTurn >= exchangeUnits.units.size()
|
||||
? exchangeUnits.units.size() - 1
|
||||
: exchangeTurn;
|
||||
|
||||
auto attacker = exchangeBattle->getForUpdate(activeUnit->unitId());
|
||||
|
||||
if(!attacker->alive())
|
||||
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;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
if(!isOur || !exchangeBattle->battleGetUnitByID(targetUnit->unitId())->alive())
|
||||
{
|
||||
auto estimateAttack = [&](const battle::Unit * u) -> float
|
||||
{
|
||||
auto stackWithBonuses = exchangeBattle->getForUpdate(u->unitId());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
if(isMovingTurm && !shooting
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Attacker is moving");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetUnit = ap.attack.defender;
|
||||
|
||||
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());
|
||||
auto score = v.trackAttack(
|
||||
attacker,
|
||||
stackWithBonuses,
|
||||
exchangeBattle->battleCanShoot(stackWithBonuses.get()),
|
||||
isOur,
|
||||
damageCache,
|
||||
hb,
|
||||
true);
|
||||
|
||||
#if BATTLE_TRACE_LEVEL>=2
|
||||
logAi->trace("Best target selector %s->%s score = %2f", attacker->getDescription(), stackWithBonuses->getDescription(), score);
|
||||
#endif
|
||||
|
||||
return score;
|
||||
};
|
||||
|
||||
auto unitsInOppositeQueueExceptInaccessible = oppositeQueue;
|
||||
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u)->bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if (!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
return vstd::contains(exchangeUnits.shooters, u);
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
if(!isOur
|
||||
&& exchangeTurn == 0
|
||||
&& exchangeUnits.units.at(exchangeTurn).at(0)->unitId() != ap.attack.attacker->unitId()
|
||||
&& !vstd::contains(exchangeUnits.enemyUnitsReachingAttacker, attacker->unitId()))
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
vstd::erase_if(unitsInOppositeQueueExceptInaccessible, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return u->unitId() == ap.attack.attacker->unitId();
|
||||
});
|
||||
}
|
||||
|
||||
if(!unitsInOppositeQueueExceptInaccessible.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(unitsInOppositeQueueExceptInaccessible, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto reachable = exchangeBattle->battleGetUnitsIf([this, &exchangeBattle, &attacker](const battle::Unit * u) -> bool
|
||||
{
|
||||
if(u->unitSide() == attacker->unitSide())
|
||||
return false;
|
||||
|
||||
if(!exchangeBattle->getForUpdate(u->unitId())->alive())
|
||||
return false;
|
||||
|
||||
if(!u->getPosition().isValid())
|
||||
return false; // e.g. tower shooters
|
||||
|
||||
return vstd::contains_if(reachabilityMap.at(u->getPosition()), [&attacker](const battle::Unit * other) -> bool
|
||||
{
|
||||
return attacker->unitId() == other->unitId();
|
||||
});
|
||||
});
|
||||
|
||||
if(!reachable.empty())
|
||||
{
|
||||
targetUnit = *vstd::maxElementByFun(reachable, estimateAttack);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if BATTLE_TRACE_LEVEL>=1
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
logAi->trace("Battle queue is empty and no reachable enemy.");
|
||||
#endif
|
||||
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
auto shooting = exchangeBattle->battleCanShoot(attacker.get());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
auto defender = exchangeBattle->getForUpdate(targetUnit->unitId());
|
||||
const int totalAttacks = attacker->getTotalAttacks(shooting);
|
||||
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
if(canUseAp && activeUnit->unitId() == ap.attack.attacker->unitId()
|
||||
&& targetUnit->unitId() == ap.attack.defender->unitId())
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
v.trackAttack(ap, exchangeBattle, damageCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i = 0; i < totalAttacks; i++)
|
||||
{
|
||||
v.trackAttack(attacker, defender, shooting, isOur, damageCache, exchangeBattle);
|
||||
|
||||
if(!attacker->alive() || !defender->alive())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!shooting)
|
||||
blockedShooters.insert(defender->unitId());
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
}
|
||||
|
||||
canUseAp = false;
|
||||
|
||||
vstd::erase_if(attackerQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
|
||||
vstd::erase_if(oppositeQueue, [&](const battle::Unit * u) -> bool
|
||||
{
|
||||
return !exchangeBattle->battleGetUnitByID(u->unitId())->alive();
|
||||
});
|
||||
exchangeBattle->nextRound();
|
||||
}
|
||||
|
||||
// avoid blocking path for stronger stack by weaker stack
|
||||
@ -679,11 +827,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)
|
||||
@ -756,7 +921,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)
|
||||
{
|
||||
@ -766,7 +931,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;
|
||||
}
|
||||
@ -816,7 +981,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)
|
||||
{
|
||||
@ -828,7 +993,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,7 +150,8 @@ public:
|
||||
BattleExchangeEvaluator(
|
||||
std::shared_ptr<CBattleInfoCallback> cb,
|
||||
std::shared_ptr<Environment> env,
|
||||
float strengthRatio): cb(cb), env(env) {
|
||||
float strengthRatio,
|
||||
int simulationTurnsCount): cb(cb), env(env), simulationTurnsCount(simulationTurnsCount){
|
||||
negativeEffectMultiplier = strengthRatio >= 1 ? 1 : strengthRatio * strengthRatio;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
@ -132,10 +132,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)
|
||||
{
|
||||
|
@ -91,7 +91,7 @@ public:
|
||||
|
||||
///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;
|
||||
|
||||
|
@ -21,8 +21,6 @@
|
||||
#include "../../lib/GameSettings.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();
|
||||
@ -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());
|
||||
}
|
||||
|
@ -89,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();
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
@ -266,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;
|
||||
|
@ -34,7 +34,6 @@ class IBattleEventsReceiver;
|
||||
class IGameEventsReceiver;
|
||||
struct ArtifactLocation;
|
||||
class BattleStateInfoForRetreat;
|
||||
class IMarket;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
@ -81,8 +80,8 @@ public:
|
||||
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;
|
||||
@ -190,8 +189,8 @@ public:
|
||||
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;
|
||||
|
12
ChangeLog.md
12
ChangeLog.md
@ -70,6 +70,18 @@
|
||||
* 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
|
||||
|
@ -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" : "用户名:",
|
||||
@ -237,6 +242,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让当前英雄重新访问地图建筑或城镇。",
|
||||
@ -286,6 +293,9 @@
|
||||
"vcmi.townHall.missingBase" : "必须先建造基础建筑 %s",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "没有可供招募的生物。",
|
||||
|
||||
"vcmi.townStructure.bank.borrow" : "你走进银行。一位银行职员看到你,说道:“我们为您提供了一个特别优惠。您可以向我们借2500金币,期限为5天。您每天需要偿还500金币。”",
|
||||
"vcmi.townStructure.bank.payBack" : "你走进银行。一位银行职员看到你,说道:“您已经获得了贷款。还清之前,不能再申请新的贷款。”",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "以下任一前提:",
|
||||
"vcmi.logicalExpressions.allOf" : "以下所有前提:",
|
||||
"vcmi.logicalExpressions.noneOf" : "与此建筑冲突:",
|
||||
|
@ -163,7 +163,7 @@
|
||||
"vcmi.systemOptions.townsGroup" : "Stadt-Bildschirm",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statistik",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Daten in Zwischenabl.",
|
||||
"vcmi.statisticWindow.tsvCopy" : "In Zwischenablage",
|
||||
"vcmi.statisticWindow.selectView" : "Ansicht wählen",
|
||||
"vcmi.statisticWindow.value" : "Wert",
|
||||
"vcmi.statisticWindow.title.overview" : "Überblick",
|
||||
|
@ -99,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"
|
||||
|
||||
@ -202,6 +201,11 @@ void CPlayerInterface::playerEndsTurn(PlayerColor player)
|
||||
{
|
||||
makingTurn = false;
|
||||
closeAllDialogs();
|
||||
|
||||
// remove all pending dialogs that do not expect query answer
|
||||
vstd::erase_if(dialogs, [](const std::shared_ptr<CInfoWindow> & window){
|
||||
return window->ID == QueryID::NONE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,9 +622,7 @@ 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();
|
||||
waitForAllDialogs();
|
||||
}
|
||||
|
||||
void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, BattleSide side, bool replayAllowed)
|
||||
@ -643,9 +645,7 @@ 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();
|
||||
waitForAllDialogs();
|
||||
|
||||
BATTLE_EVENT_POSSIBLE_RETURN;
|
||||
}
|
||||
@ -1012,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();
|
||||
@ -1631,14 +1631,14 @@ 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)
|
||||
if (market->allowsTrade(EMarketMode::ARTIFACT_EXP) && market->getArtifactsStorage() == 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
|
||||
@ -1653,8 +1653,17 @@ void CPlayerInterface::showMarketWindow(const IMarket *market, const CGHeroInsta
|
||||
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)
|
||||
@ -1759,6 +1768,9 @@ void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
|
||||
|
||||
void CPlayerInterface::waitForAllDialogs()
|
||||
{
|
||||
if (!makingTurn)
|
||||
return;
|
||||
|
||||
while(!dialogs.empty())
|
||||
{
|
||||
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
|
||||
|
@ -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
|
||||
|
@ -35,6 +35,7 @@
|
||||
#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"
|
||||
@ -44,7 +45,6 @@
|
||||
#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"
|
||||
|
||||
@ -56,61 +56,6 @@
|
||||
|
||||
#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)
|
||||
@ -148,7 +93,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)
|
||||
@ -159,7 +103,6 @@ CServerHandler::CServerHandler()
|
||||
, client(nullptr)
|
||||
{
|
||||
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
|
||||
registerTypesLobbyPacks(*applier);
|
||||
}
|
||||
|
||||
void CServerHandler::threadRunNetwork()
|
||||
@ -320,8 +263,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();
|
||||
}
|
||||
|
||||
@ -960,7 +903,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);
|
||||
|
@ -31,8 +31,6 @@ struct CPackForClient;
|
||||
|
||||
class HighScoreParameter;
|
||||
|
||||
template<typename T> class CApplier;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class CClient;
|
||||
@ -102,7 +100,6 @@ 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;
|
||||
|
@ -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)
|
||||
ApplyClientNetPackVisitor afterVisitor(*this, *gameState());
|
||||
ApplyFirstClientNetPackVisitor beforeVisitor(*this, *gameState());
|
||||
|
||||
pack->visit(beforeVisitor);
|
||||
logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
|
||||
{
|
||||
apply->applyOnClBefore(this, pack);
|
||||
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);
|
||||
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());
|
||||
boost::unique_lock lock(CGameState::mutex);
|
||||
gs->apply(pack);
|
||||
}
|
||||
logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
|
||||
pack->visit(afterVisitor);
|
||||
logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
|
||||
|
||||
delete pack;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
@ -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;
|
||||
|
||||
|
@ -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"
|
||||
@ -999,7 +998,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 +1008,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;
|
||||
|
@ -68,7 +68,10 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
|
||||
onLoginModeChanged(0); // call it manually to disable widgets - toggleMode will not emit this call if this is currently selected option
|
||||
}
|
||||
else
|
||||
{
|
||||
toggleMode->setSelected(1);
|
||||
onLoginModeChanged(1);
|
||||
}
|
||||
|
||||
filledBackground->setPlayerColor(PlayerColor(1));
|
||||
inputUsername->setCallback([this](const std::string & text)
|
||||
|
@ -286,5 +286,5 @@ GlobalLobbyMatchCard::GlobalLobbyMatchCard(GlobalLobbyWindow * window, const Glo
|
||||
opponentDescription.replaceNumber(matchDescription.participants.size());
|
||||
}
|
||||
|
||||
labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString());
|
||||
labelMatchOpponent = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, opponentDescription.toString(), 120);
|
||||
}
|
||||
|
@ -86,14 +86,14 @@ CBonusSelection::CBonusSelection()
|
||||
buttonVideo = std::make_shared<CButton>(Point(705, 214), AnimationPath::builtin("CBVIDEB.DEF"), CButton::tooltip(), playVideo, EShortcut::LOBBY_REPLAY_VIDEO);
|
||||
buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
|
||||
|
||||
campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
|
||||
campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName(), 250);
|
||||
|
||||
iconsMapSizes = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 735, 26);
|
||||
|
||||
labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
|
||||
campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescriptionTranslated(), Rect(480, 86, 286, 117), 1);
|
||||
|
||||
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated());
|
||||
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getNameTranslated(), 285);
|
||||
labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
|
||||
mapDescription = std::make_shared<CTextBox>("", Rect(480, 278, 292, 108), 1);
|
||||
|
||||
|
@ -135,7 +135,7 @@ InfoCard::InfoCard()
|
||||
|
||||
labelSaveDate = std::make_shared<CLabel>(310, 38, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE);
|
||||
labelMapSize = std::make_shared<CLabel>(333, 56, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE);
|
||||
mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, "", 285);
|
||||
mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, "", SEL->screenType == ESelectionScreen::campaignList ? 325 : 285);
|
||||
Rect descriptionRect(26, 149, 320, 115);
|
||||
mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
|
||||
playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
|
||||
|
@ -47,7 +47,6 @@
|
||||
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/campaign/CampaignHandler.h"
|
||||
#include "../../lib/serializer/CTypeList.h"
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../../lib/filesystem/CCompressedStream.h"
|
||||
#include "../../lib/mapping/CMapInfo.h"
|
||||
|
@ -130,13 +130,9 @@ TIcons CStatisticScreen::extractIcons() const
|
||||
std::sort(tmpData.begin(), tmpData.end(), [](const StatisticDataSetEntry & v1, const StatisticDataSetEntry & v2){ return v1.player == v2.player ? v1.day < v2.day : v1.player < v2.player; });
|
||||
|
||||
auto imageTown = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 3, 0, EImageBlitMode::COLORKEY);
|
||||
imageTown->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
auto imageBattle = GH.renderHandler().loadImage(AnimationPath::builtin("cradvntr"), 5, 0, EImageBlitMode::COLORKEY);
|
||||
imageBattle->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
auto imageDefeated = GH.renderHandler().loadImage(AnimationPath::builtin("tpthchk"), 1, 0, EImageBlitMode::COLORKEY);
|
||||
imageDefeated->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
auto imageDefeated = GH.renderHandler().loadImage(AnimationPath::builtin("crcombat"), 0, 0, EImageBlitMode::COLORKEY);
|
||||
auto imageGrail = GH.renderHandler().loadImage(AnimationPath::builtin("vwsymbol"), 2, 0, EImageBlitMode::COLORKEY);
|
||||
imageGrail->scaleTo(Point(CHART_ICON_SIZE, CHART_ICON_SIZE));
|
||||
|
||||
std::map<PlayerColor, bool> foundDefeated;
|
||||
std::map<PlayerColor, bool> foundGrail;
|
||||
@ -273,7 +269,11 @@ OverviewPanel::OverviewPanel(Rect position, std::string title, const StatisticDa
|
||||
},
|
||||
{
|
||||
CGI->generaltexth->translate("vcmi.statisticWindow.param.daysSurvived"), [this](PlayerColor color){
|
||||
return CStatisticScreen::getDay(playerDataFilter(color).size());
|
||||
auto playerData = playerDataFilter(color);
|
||||
for(int i = 0; i < playerData.size(); i++)
|
||||
if(playerData[i].status == EPlayerStatus::LOSER)
|
||||
return CStatisticScreen::getDay(i + 1);
|
||||
return CStatisticScreen::getDay(playerData.size());
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -424,12 +424,25 @@ void OverviewPanel::update(int to)
|
||||
}
|
||||
}
|
||||
|
||||
int computeGridStep(int maxAmount, int linesLimit)
|
||||
{
|
||||
for (int lineInterval = 1;;lineInterval *= 10)
|
||||
{
|
||||
for (int factor : { 1, 2, 5 } )
|
||||
{
|
||||
int lineIntervalToTest = lineInterval * factor;
|
||||
if (maxAmount / lineIntervalToTest <= linesLimit)
|
||||
return lineIntervalToTest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY)
|
||||
: CIntObject(), maxVal(0), maxDay(0)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
addUsedEvents(LCLICK | MOVE);
|
||||
addUsedEvents(LCLICK | MOVE | GESTURE);
|
||||
|
||||
pos = position + pos.topLeft();
|
||||
|
||||
@ -455,15 +468,47 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
|
||||
maxDay = line.second.size();
|
||||
}
|
||||
|
||||
//calculate nice maxVal
|
||||
int gridLineCount = 10;
|
||||
int gridStep = computeGridStep(maxVal, gridLineCount);
|
||||
niceMaxVal = gridStep * std::ceil(maxVal / gridStep);
|
||||
|
||||
// calculate points in chart
|
||||
auto getPoint = [this](int i, std::vector<float> data){
|
||||
float x = (static_cast<float>(chartArea.w) / static_cast<float>(maxDay - 1)) * static_cast<float>(i);
|
||||
float y = static_cast<float>(chartArea.h) - (static_cast<float>(chartArea.h) / niceMaxVal) * data[i];
|
||||
return Point(x, y);
|
||||
};
|
||||
|
||||
// draw grid (vertical lines)
|
||||
int dayGridInterval = maxDay < 700 ? 7 : 28;
|
||||
for(const auto & line : data)
|
||||
{
|
||||
for(int i = 0; i < line.second.size(); i += dayGridInterval)
|
||||
{
|
||||
Point p = getPoint(i, line.second) + chartArea.topLeft();
|
||||
canvas->addLine(Point(p.x, chartArea.topLeft().y), Point(p.x, chartArea.topLeft().y + chartArea.h), ColorRGBA(70, 70, 70));
|
||||
}
|
||||
}
|
||||
|
||||
// draw grid (horizontal lines)
|
||||
if(maxVal > 0)
|
||||
{
|
||||
int gridStepPx = int((static_cast<float>(chartArea.h) / niceMaxVal) * gridStep);
|
||||
for(int i = 0; i < std::ceil(maxVal / gridStep) + 1; i++)
|
||||
{
|
||||
canvas->addLine(chartArea.topLeft() + Point(0, chartArea.h - gridStepPx * i), chartArea.topLeft() + Point(chartArea.w, chartArea.h - gridStepPx * i), ColorRGBA(70, 70, 70));
|
||||
layout.emplace_back(std::make_shared<CLabel>(chartArea.topLeft().x - 5, chartArea.topLeft().y + 10 + chartArea.h - gridStepPx * i, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, TextOperations::formatMetric(i * gridStep, 5)));
|
||||
}
|
||||
}
|
||||
|
||||
// draw
|
||||
for(const auto & line : data)
|
||||
{
|
||||
Point lastPoint(-1, -1);
|
||||
for(int i = 0; i < line.second.size(); i++)
|
||||
{
|
||||
float x = (static_cast<float>(chartArea.w) / static_cast<float>(maxDay - 1)) * static_cast<float>(i);
|
||||
float y = static_cast<float>(chartArea.h) - (static_cast<float>(chartArea.h) / maxVal) * line.second[i];
|
||||
Point p = Point(x, y) + chartArea.topLeft();
|
||||
Point p = getPoint(i, line.second) + chartArea.topLeft();
|
||||
|
||||
if(lastPoint.x != -1)
|
||||
canvas->addLine(lastPoint, p, line.first);
|
||||
@ -472,7 +517,7 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
|
||||
for(auto & icon : icons)
|
||||
if(std::get<0>(icon) == line.first && std::get<1>(icon) == i + 1) // color && day
|
||||
{
|
||||
pictures.emplace_back(std::make_shared<CPicture>(std::get<2>(icon), Point(x - (CHART_ICON_SIZE / 2), y - (CHART_ICON_SIZE / 2)) + chartArea.topLeft()));
|
||||
pictures.emplace_back(std::make_shared<CPicture>(std::get<2>(icon), Point(p.x - (std::get<2>(icon)->width() / 2), p.y - (std::get<2>(icon)->height() / 2))));
|
||||
pictures.back()->addRClickCallback([icon](){ CRClickPopup::createAndPush(std::get<3>(icon)); });
|
||||
}
|
||||
|
||||
@ -484,12 +529,8 @@ LineChart::LineChart(Rect position, std::string title, TData data, TIcons icons,
|
||||
canvas->addLine(chartArea.topLeft() + Point(0, -10), chartArea.topLeft() + Point(0, chartArea.h + 10), Colors::WHITE);
|
||||
canvas->addLine(chartArea.topLeft() + Point(-10, chartArea.h), chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h), Colors::WHITE);
|
||||
|
||||
Point p = chartArea.topLeft() + Point(-5, chartArea.h + 10);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, "0"));
|
||||
p = chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h + 10);
|
||||
Point p = chartArea.topLeft() + Point(chartArea.w + 10, chartArea.h + 10);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CStatisticScreen::getDay(maxDay)));
|
||||
p = chartArea.topLeft() + Point(-5, -10);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_SMALL, ETextAlignment::CENTERRIGHT, Colors::WHITE, std::to_string(static_cast<int>(maxVal))));
|
||||
p = chartArea.bottomLeft() + Point(chartArea.w / 2, + 20);
|
||||
layout.emplace_back(std::make_shared<CLabel>(p.x, p.y, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("core.genrltxt.64")));
|
||||
}
|
||||
@ -502,8 +543,8 @@ void LineChart::updateStatusBar(const Point & cursorPosition)
|
||||
statusBar->setEnabled(r.isInside(cursorPosition));
|
||||
if(r.isInside(cursorPosition))
|
||||
{
|
||||
float x = (static_cast<float>(maxDay) / static_cast<float>(chartArea.w)) * (static_cast<float>(cursorPosition.x) - static_cast<float>(r.x)) + 1.0f;
|
||||
float y = maxVal - (maxVal / static_cast<float>(chartArea.h)) * (static_cast<float>(cursorPosition.y) - static_cast<float>(r.y));
|
||||
float x = (static_cast<float>(maxDay - 1) / static_cast<float>(chartArea.w)) * (static_cast<float>(cursorPosition.x) - static_cast<float>(r.x)) + 1.0f;
|
||||
float y = niceMaxVal - (niceMaxVal / static_cast<float>(chartArea.h)) * (static_cast<float>(cursorPosition.y) - static_cast<float>(r.y));
|
||||
statusBar->write(CGI->generaltexth->translate("core.genrltxt.64") + ": " + CStatisticScreen::getDay(x) + " " + CGI->generaltexth->translate("vcmi.statisticWindow.value") + ": " + (static_cast<int>(y) > 0 ? std::to_string(static_cast<int>(y)) : std::to_string(y)));
|
||||
}
|
||||
setRedrawParent(true);
|
||||
@ -515,7 +556,7 @@ void LineChart::mouseMoved(const Point & cursorPosition, const Point & lastUpdat
|
||||
updateStatusBar(cursorPosition);
|
||||
}
|
||||
|
||||
void LineChart::clickPressed(const Point & cursorPosition)
|
||||
void LineChart::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
|
||||
{
|
||||
updateStatusBar(cursorPosition);
|
||||
updateStatusBar(currentPosition);
|
||||
}
|
||||
|
@ -24,8 +24,6 @@ class CPicture;
|
||||
using TData = std::vector<std::pair<ColorRGBA, std::vector<float>>>;
|
||||
using TIcons = std::vector<std::tuple<ColorRGBA, int, std::shared_ptr<IImage>, std::string>>; // Color, Day, Image, Helptext
|
||||
|
||||
const int CHART_ICON_SIZE = 32;
|
||||
|
||||
class CStatisticScreen : public CWindowObject
|
||||
{
|
||||
enum Content {
|
||||
@ -123,6 +121,7 @@ class LineChart : public CIntObject
|
||||
|
||||
Rect chartArea;
|
||||
float maxVal;
|
||||
int niceMaxVal;
|
||||
int maxDay;
|
||||
|
||||
void updateStatusBar(const Point & cursorPosition);
|
||||
@ -130,5 +129,5 @@ public:
|
||||
LineChart(Rect position, std::string title, TData data, TIcons icons, float maxY);
|
||||
|
||||
void mouseMoved(const Point & cursorPosition, const Point & lastUpdateDistance) override;
|
||||
void clickPressed(const Point & cursorPosition) override;
|
||||
void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
|
||||
};
|
||||
|
@ -233,7 +233,7 @@ std::string CComponent::getDescription() const
|
||||
return description;
|
||||
}
|
||||
case ComponentType::SPELL:
|
||||
return CGI->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(data.value.value_or(0));
|
||||
return CGI->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(std::max(0, data.value.value_or(0)));
|
||||
case ComponentType::MORALE:
|
||||
return CGI->generaltexth->heroscrn[ 4 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
|
||||
case ComponentType::LUCK:
|
||||
@ -293,7 +293,7 @@ std::string CComponent::getSubtitle() const
|
||||
return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
|
||||
case ComponentType::SPELL_SCROLL:
|
||||
case ComponentType::SPELL:
|
||||
if (data.value < 0)
|
||||
if (data.value.value_or(0) < 0)
|
||||
return "{#A9A9A9|" + CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated() + "}";
|
||||
else
|
||||
return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
|
||||
|
@ -464,7 +464,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & town : towns)
|
||||
{
|
||||
if(town->id == townId && town->builtBuildings.count(BuildingID::TAVERN))
|
||||
if(town->id == townId && town->hasBuilt(BuildingID::TAVERN))
|
||||
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
|
||||
}
|
||||
}, [town]{
|
||||
@ -476,7 +476,7 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & town : towns)
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
|
||||
if(town->hasBuilt(BuildingID::MARKETPLACE))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
|
||||
return;
|
||||
|
@ -24,16 +24,15 @@
|
||||
#include "../../../lib/networkPacks/ArtifactLocation.h"
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../../lib/mapObjects/CGMarket.h"
|
||||
#include "../../../lib/mapObjects/IMarket.h"
|
||||
|
||||
CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance * hero)
|
||||
: CMarketBase(market, hero)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
assert(dynamic_cast<const CGArtifactsAltar*>(market));
|
||||
auto altarObj = dynamic_cast<const CGArtifactsAltar*>(market);
|
||||
altarArtifacts = altarObj;
|
||||
assert(market->getArtifactsStorage());
|
||||
altarArtifactsStorage = market->getArtifactsStorage();
|
||||
|
||||
deal = std::make_shared<CButton>(Point(269, 520), AnimationPath::builtin("ALTSACR.DEF"),
|
||||
CGI->generaltexth->zelp[585], [this]() {CAltarArtifacts::makeDeal(); }, EShortcut::MARKET_DEAL);
|
||||
@ -51,7 +50,7 @@ CAltarArtifacts::CAltarArtifacts(const IMarket * market, const CGHeroInstance *
|
||||
// Hero's artifacts
|
||||
heroArts = std::make_shared<CArtifactsOfHeroAltar>(Point(-365, -11));
|
||||
heroArts->setHero(hero);
|
||||
heroArts->altarId = altarObj->id;
|
||||
heroArts->altarId = market->getObjInstanceID();
|
||||
|
||||
// Altar
|
||||
offerTradePanel = std::make_shared<ArtifactsAltarPanel>([this](const std::shared_ptr<CTradeableItem> & altarSlot)
|
||||
@ -104,7 +103,7 @@ void CAltarArtifacts::makeDeal()
|
||||
{
|
||||
positions.push_back(artInst->getId());
|
||||
}
|
||||
LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_EXP, positions, std::vector<TradeItemBuy>(), std::vector<ui32>(), hero);
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_EXP, positions, std::vector<TradeItemBuy>(), std::vector<ui32>(), hero);
|
||||
deselect();
|
||||
}
|
||||
|
||||
@ -125,7 +124,7 @@ std::shared_ptr<CArtifactsOfHeroAltar> CAltarArtifacts::getAOHset() const
|
||||
|
||||
void CAltarArtifacts::updateAltarSlots()
|
||||
{
|
||||
assert(altarArtifacts->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
|
||||
assert(altarArtifactsStorage->artifactsInBackpack.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
|
||||
assert(tradeSlotsMap.size() <= GameConstants::ALTAR_ARTIFACTS_SLOTS);
|
||||
|
||||
auto tradeSlotsMapNewArts = tradeSlotsMap;
|
||||
@ -146,12 +145,12 @@ void CAltarArtifacts::updateAltarSlots()
|
||||
for(auto & tradeSlot : tradeSlotsMapNewArts)
|
||||
{
|
||||
assert(tradeSlot.first->id == -1);
|
||||
assert(altarArtifacts->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
|
||||
assert(altarArtifactsStorage->getArtPos(tradeSlot.second) != ArtifactPosition::PRE_FIRST);
|
||||
tradeSlot.first->setID(tradeSlot.second->getTypeId().num);
|
||||
tradeSlot.first->subtitle->setText(std::to_string(calcExpCost(tradeSlot.second->getTypeId())));
|
||||
}
|
||||
|
||||
auto newArtsFromBulkMove = altarArtifacts->artifactsInBackpack;
|
||||
auto newArtsFromBulkMove = altarArtifactsStorage->artifactsInBackpack;
|
||||
for(const auto & [altarSlot, art] : tradeSlotsMap)
|
||||
{
|
||||
newArtsFromBulkMove.erase(std::remove_if(newArtsFromBulkMove.begin(), newArtsFromBulkMove.end(), [artForRemove = art](auto & slotInfo)
|
||||
@ -179,7 +178,7 @@ void CAltarArtifacts::putBackArtifacts()
|
||||
{
|
||||
// TODO: If the backpack capacity limit is enabled, artifacts may remain on the altar.
|
||||
// Perhaps should be erased in CGameHandler::objectVisitEnded if id of visited object will be available
|
||||
if(!altarArtifacts->artifactsInBackpack.empty())
|
||||
if(!altarArtifactsStorage->artifactsInBackpack.empty())
|
||||
LOCPLINT->cb->bulkMoveArtifacts(heroArts->altarId, heroArts->getHero()->id, false, true, true);
|
||||
}
|
||||
|
||||
@ -200,7 +199,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
|
||||
|
||||
if(const auto pickedArtInst = heroArts->getPickedArtifact())
|
||||
{
|
||||
if(pickedArtInst->canBePutAt(altarArtifacts))
|
||||
if(pickedArtInst->canBePutAt(altarArtifactsStorage.get()))
|
||||
{
|
||||
if(pickedArtInst->artType->isTradable())
|
||||
{
|
||||
@ -221,7 +220,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
|
||||
else if(altarSlot->id != -1)
|
||||
{
|
||||
assert(tradeSlotsMap.at(altarSlot));
|
||||
const auto slot = altarArtifacts->getArtPos(tradeSlotsMap.at(altarSlot));
|
||||
const auto slot = altarArtifactsStorage->getArtPos(tradeSlotsMap.at(altarSlot));
|
||||
assert(slot != ArtifactPosition::PRE_FIRST);
|
||||
LOCPLINT->cb->swapArtifacts(ArtifactLocation(heroArts->altarId, slot),
|
||||
ArtifactLocation(hero->id, GH.isKeyboardCtrlDown() ? ArtifactPosition::FIRST_AVAILABLE : ArtifactPosition::TRANSITION_POS));
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
void putBackArtifacts();
|
||||
|
||||
private:
|
||||
const CArtifactSet * altarArtifacts;
|
||||
std::shared_ptr<CArtifactSet> altarArtifactsStorage;
|
||||
std::shared_ptr<CButton> sacrificeBackpackButton;
|
||||
std::shared_ptr<CArtifactsOfHeroAltar> heroArts;
|
||||
std::map<std::shared_ptr<CTradeableItem>, const CArtifactInstance*> tradeSlotsMap;
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../../lib/mapObjects/CGMarket.h"
|
||||
#include "../../../lib/mapObjects/IMarket.h"
|
||||
|
||||
CAltarCreatures::CAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
|
||||
: CMarketBase(market, hero)
|
||||
@ -157,7 +157,7 @@ void CAltarCreatures::makeDeal()
|
||||
}
|
||||
}
|
||||
|
||||
LOCPLINT->cb->trade(market, EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero);
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_EXP, ids, {}, toSacrifice, hero);
|
||||
|
||||
for(int & units : unitsOnAltar)
|
||||
units = 0;
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "StdInc.h"
|
||||
#include "CArtifactsBuying.h"
|
||||
|
||||
#include "../../gui/CGuiHandler.h"
|
||||
#include "../../gui/Shortcut.h"
|
||||
#include "../../widgets/Buttons.h"
|
||||
#include "../../widgets/TextControls.h"
|
||||
@ -21,24 +20,16 @@
|
||||
|
||||
#include "../../../CCallback.h"
|
||||
|
||||
#include "../../../lib/entities/building/CBuilding.h"
|
||||
#include "../../../lib/entities/faction/CTownHandler.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../../lib/mapObjects/CGMarket.h"
|
||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../../lib/mapObjects/IMarket.h"
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
|
||||
CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
|
||||
CArtifactsBuying::CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title)
|
||||
: CMarketBase(market, hero)
|
||||
, CResourcesSelling([this](const std::shared_ptr<CTradeableItem> & heroSlot){CArtifactsBuying::onSlotClickPressed(heroSlot, bidTradePanel);})
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
std::string title;
|
||||
if(auto townMarket = dynamic_cast<const CGTownInstance*>(market))
|
||||
title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
|
||||
else
|
||||
title = CGI->generaltexth->allTexts[349];
|
||||
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
|
||||
deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
|
||||
CGI->generaltexth->zelp[595], [this](){CArtifactsBuying::makeDeal();}, EShortcut::MARKET_DEAL);
|
||||
@ -77,7 +68,7 @@ void CArtifactsBuying::makeDeal()
|
||||
{
|
||||
if(ArtifactID(offerTradePanel->getSelectedItemId()).toArtifact()->canBePutAt(hero))
|
||||
{
|
||||
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT, GameResID(bidTradePanel->getSelectedItemId()),
|
||||
ArtifactID(offerTradePanel->getSelectedItemId()), offerQty, hero);
|
||||
CMarketTraderText::makeDeal();
|
||||
deselect();
|
||||
|
@ -14,7 +14,7 @@
|
||||
class CArtifactsBuying : public CResourcesSelling, public CMarketTraderText
|
||||
{
|
||||
public:
|
||||
CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero);
|
||||
CArtifactsBuying(const IMarket * market, const CGHeroInstance * hero, const std::string & title);
|
||||
void deselect() override;
|
||||
void makeDeal() override;
|
||||
|
||||
|
@ -22,14 +22,11 @@
|
||||
#include "../../../CCallback.h"
|
||||
|
||||
#include "../../../lib/CArtifactInstance.h"
|
||||
#include "../../../lib/entities/building/CBuilding.h"
|
||||
#include "../../../lib/entities/faction/CTownHandler.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../../lib/mapObjects/CGMarket.h"
|
||||
#include "../../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../../lib/mapObjects/IMarket.h"
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
|
||||
CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero)
|
||||
CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title)
|
||||
: CMarketBase(market, hero)
|
||||
, CResourcesBuying(
|
||||
[this](const std::shared_ptr<CTradeableItem> & resSlot){CArtifactsSelling::onSlotClickPressed(resSlot, offerTradePanel);},
|
||||
@ -37,12 +34,6 @@ CArtifactsSelling::CArtifactsSelling(const IMarket * market, const CGHeroInstanc
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
std::string title;
|
||||
if(const auto townMarket = dynamic_cast<const CGTownInstance*>(market))
|
||||
title = (*CGI->townh)[townMarket->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
|
||||
else if(const auto mapMarket = dynamic_cast<const CGMarket*>(market))
|
||||
title = mapMarket->title;
|
||||
|
||||
labels.emplace_back(std::make_shared<CLabel>(titlePos.x, titlePos.y, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, title));
|
||||
labels.push_back(std::make_shared<CLabel>(155, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated())));
|
||||
deal = std::make_shared<CButton>(dealButtonPos, AnimationPath::builtin("TPMRKB.DEF"),
|
||||
@ -87,7 +78,8 @@ void CArtifactsSelling::makeDeal()
|
||||
{
|
||||
const auto art = hero->getArt(selectedHeroSlot);
|
||||
assert(art);
|
||||
LOCPLINT->cb->trade(market, EMarketMode::ARTIFACT_RESOURCE, art->getId(), GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE, art->getId(),
|
||||
GameResID(offerTradePanel->getSelectedItemId()), offerQty, hero);
|
||||
CMarketTraderText::makeDeal();
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
class CArtifactsSelling : public CResourcesBuying, public CMarketTraderText
|
||||
{
|
||||
public:
|
||||
CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero);
|
||||
CArtifactsSelling(const IMarket * market, const CGHeroInstance * hero, const std::string & title);
|
||||
void deselect() override;
|
||||
void makeDeal() override;
|
||||
void updateShowcases() override;
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../../lib/mapObjects/CGMarket.h"
|
||||
#include "../../../lib/mapObjects/IMarket.h"
|
||||
|
||||
CFreelancerGuild::CFreelancerGuild(const IMarket * market, const CGHeroInstance * hero)
|
||||
: CMarketBase(market, hero)
|
||||
@ -69,7 +69,7 @@ void CFreelancerGuild::makeDeal()
|
||||
{
|
||||
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
|
||||
{
|
||||
LOCPLINT->cb->trade(market, EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_RESOURCE, SlotID(bidTradePanel->highlightedSlot->serial), GameResID(offerTradePanel->getSelectedItemId()), bidQty * toTrade, hero);
|
||||
CMarketTraderText::makeDeal();
|
||||
deselect();
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
#include "../../../CCallback.h"
|
||||
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../../lib/mapObjects/CGMarket.h"
|
||||
#include "../../../lib/mapObjects/IMarket.h"
|
||||
|
||||
CMarketResources::CMarketResources(const IMarket * market, const CGHeroInstance * hero)
|
||||
: CMarketBase(market, hero)
|
||||
@ -60,7 +60,7 @@ void CMarketResources::makeDeal()
|
||||
{
|
||||
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
|
||||
{
|
||||
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(bidTradePanel->getSelectedItemId()),
|
||||
GameResID(offerTradePanel->highlightedSlot->id), bidQty * toTrade, hero);
|
||||
CMarketTraderText::makeDeal();
|
||||
deselect();
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "../../../CCallback.h"
|
||||
|
||||
#include "../../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../../lib/mapObjects/IMarket.h"
|
||||
#include "../../../lib/texts/MetaString.h"
|
||||
|
||||
CTransferResources::CTransferResources(const IMarket * market, const CGHeroInstance * hero)
|
||||
@ -63,7 +64,7 @@ void CTransferResources::makeDeal()
|
||||
{
|
||||
if(auto toTrade = offerSlider->getValue(); toTrade != 0)
|
||||
{
|
||||
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_PLAYER, GameResID(bidTradePanel->getSelectedItemId()),
|
||||
PlayerColor(offerTradePanel->getSelectedItemId()), toTrade, hero);
|
||||
CMarketTraderText::makeDeal();
|
||||
deselect();
|
||||
|
@ -586,9 +586,9 @@ void CCastleBuildings::recreate()
|
||||
|
||||
//Generate buildings list
|
||||
|
||||
auto buildingsCopy = town->builtBuildings;// a bit modified copy of built buildings
|
||||
auto buildingsCopy = town->getBuildings();// a bit modified copy of built buildings
|
||||
|
||||
if(vstd::contains(town->builtBuildings, BuildingID::SHIPYARD))
|
||||
if(town->hasBuilt(BuildingID::SHIPYARD))
|
||||
{
|
||||
auto bayPos = town->bestLocation();
|
||||
if(!bayPos.valid())
|
||||
@ -996,7 +996,7 @@ void CCastleBuildings::enterMagesGuild()
|
||||
void CCastleBuildings::enterTownHall()
|
||||
{
|
||||
if(town->visitingHero && town->visitingHero->hasArt(ArtifactID::GRAIL) &&
|
||||
!vstd::contains(town->builtBuildings, BuildingID::GRAIL)) //hero has grail, but town does not have it
|
||||
!town->hasBuilt(BuildingID::GRAIL)) //hero has grail, but town does not have it
|
||||
{
|
||||
if(!vstd::contains(town->forbiddenBuildings, BuildingID::GRAIL))
|
||||
{
|
||||
@ -1033,7 +1033,7 @@ void CCastleBuildings::enterAnyThievesGuild()
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & ownedTown : towns)
|
||||
{
|
||||
if(ownedTown->builtBuildings.count(BuildingID::TAVERN))
|
||||
if(ownedTown->hasBuilt(BuildingID::TAVERN))
|
||||
{
|
||||
LOCPLINT->showThievesGuildWindow(ownedTown);
|
||||
return;
|
||||
@ -1059,7 +1059,7 @@ void CCastleBuildings::enterBank()
|
||||
|
||||
void CCastleBuildings::enterAnyMarket()
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
|
||||
if(town->hasBuilt(BuildingID::MARKETPLACE))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
|
||||
return;
|
||||
@ -1068,7 +1068,7 @@ void CCastleBuildings::enterAnyMarket()
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & town : towns)
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
|
||||
if(town->hasBuilt(BuildingID::MARKETPLACE))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
|
||||
return;
|
||||
@ -1385,7 +1385,7 @@ void CCastleInterface::recreateIcons()
|
||||
fastMarket = std::make_shared<LRClickableArea>(Rect(163, 410, 64, 42), [this]() { builds->enterAnyMarket(); });
|
||||
fastTavern = std::make_shared<LRClickableArea>(Rect(15, 387, 58, 64), [&]()
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::TAVERN))
|
||||
if(town->hasBuilt(BuildingID::TAVERN))
|
||||
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
|
||||
}, [this]{
|
||||
if(!town->town->faction->getDescriptionTranslated().empty())
|
||||
@ -1563,7 +1563,7 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
|
||||
}
|
||||
|
||||
const CBuilding * current = town->town->buildings.at(buildingID);
|
||||
if(vstd::contains(town->builtBuildings, buildingID))
|
||||
if(town->hasBuilt(buildingID))
|
||||
{
|
||||
building = current;
|
||||
}
|
||||
@ -1776,7 +1776,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
|
||||
{
|
||||
BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1);
|
||||
|
||||
if(vstd::contains(town->builtBuildings, dwelling))
|
||||
if(town->hasBuilt(dwelling))
|
||||
buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 1));
|
||||
else
|
||||
buildingID = BuildingID(BuildingID::getDwellingFromLevel(i, 0));
|
||||
@ -1841,7 +1841,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
|
||||
buildingIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
|
||||
buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152);
|
||||
|
||||
if(vstd::contains(town->builtBuildings, getMyBuilding()->bid))
|
||||
if(town->hasBuilt(getMyBuilding()->bid))
|
||||
{
|
||||
ui32 available = town->creatures[level].first;
|
||||
std::string availableText = CGI->generaltexth->allTexts[217]+ std::to_string(available);
|
||||
@ -1894,8 +1894,9 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
|
||||
{
|
||||
if (town->hasBuilt(myID))
|
||||
build = town->town->buildings.at(myID);
|
||||
myID.advance(town->town->creatures.size());
|
||||
BuildingID::advanceDwelling(myID);
|
||||
}
|
||||
|
||||
return build;
|
||||
}
|
||||
|
||||
|
@ -619,6 +619,7 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
|
||||
parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
|
||||
parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT);
|
||||
parent->stackArtifactHelp->component.subType = art->artType->getId();
|
||||
parent->stackArtifactHelp->text = art->getDescription();
|
||||
|
||||
if(parent->info->owner)
|
||||
{
|
||||
|
@ -822,7 +822,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
|
||||
|
||||
fastTavern = std::make_shared<LRClickableArea>(Rect(5, 6, 58, 64), [&]()
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::TAVERN))
|
||||
if(town->hasBuilt(BuildingID::TAVERN))
|
||||
LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
|
||||
}, [&]{
|
||||
if(!town->town->faction->getDescriptionTranslated().empty())
|
||||
@ -833,7 +833,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
|
||||
std::vector<const CGTownInstance*> towns = LOCPLINT->cb->getTownsInfo(true);
|
||||
for(auto & town : towns)
|
||||
{
|
||||
if(town->builtBuildings.count(BuildingID::MARKETPLACE))
|
||||
if(town->hasBuilt(BuildingID::MARKETPLACE))
|
||||
{
|
||||
GH.windows().createAndPushWindow<CMarketWindow>(town, nullptr, nullptr, EMarketMode::RESOURCE_RESOURCE);
|
||||
return;
|
||||
|
@ -27,11 +27,14 @@
|
||||
#include "../CGameInfo.h"
|
||||
#include "../CPlayerInterface.h"
|
||||
|
||||
#include "../../lib/entities/building/CBuilding.h"
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjects/CGMarket.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
|
||||
#include "../../CCallback.h"
|
||||
|
||||
CMarketWindow::CMarketWindow(const IMarket * market, const CGHeroInstance * hero, const std::function<void()> & onWindowClosed, EMarketMode mode)
|
||||
: CWindowObject(PLAYER_COLORED)
|
||||
, windowClosedCallback(onWindowClosed)
|
||||
@ -111,6 +114,11 @@ void CMarketWindow::createChangeModeButtons(EMarketMode currentMode, const IMark
|
||||
if(!market->allowsTrade(modeButton))
|
||||
return false;
|
||||
|
||||
if(currentMode == EMarketMode::ARTIFACT_EXP && modeButton != EMarketMode::CREATURE_EXP)
|
||||
return false;
|
||||
if(currentMode == EMarketMode::CREATURE_EXP && modeButton != EMarketMode::ARTIFACT_EXP)
|
||||
return false;
|
||||
|
||||
if(modeButton == EMarketMode::RESOURCE_RESOURCE || modeButton == EMarketMode::RESOURCE_PLAYER)
|
||||
{
|
||||
if(const auto town = dynamic_cast<const CGTownInstance*>(market))
|
||||
@ -175,12 +183,28 @@ void CMarketWindow::initWidgetInternals(const EMarketMode mode, const std::pair<
|
||||
redraw();
|
||||
}
|
||||
|
||||
std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const
|
||||
{
|
||||
assert(LOCPLINT->cb->getMarket(marketId));
|
||||
assert(vstd::contains(LOCPLINT->cb->getMarket(marketId)->availableModes(), mode));
|
||||
|
||||
if(const auto town = LOCPLINT->cb->getTown(marketId))
|
||||
{
|
||||
for(const auto & buildingId : town->getBuildings())
|
||||
{
|
||||
if(const auto building = town->town->buildings.at(buildingId); vstd::contains(building->marketModes, mode))
|
||||
return building->getNameTranslated();
|
||||
}
|
||||
}
|
||||
return LOCPLINT->cb->getObj(marketId)->getObjectName();
|
||||
}
|
||||
|
||||
void CMarketWindow::createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
background = createBg(ImagePath::builtin("TPMRKABS.bmp"), PLAYER_COLORED);
|
||||
marketWidget = std::make_shared<CArtifactsBuying>(market, hero);
|
||||
marketWidget = std::make_shared<CArtifactsBuying>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::RESOURCE_ARTIFACT));
|
||||
initWidgetInternals(EMarketMode::RESOURCE_ARTIFACT, CGI->generaltexth->zelp[600]);
|
||||
}
|
||||
|
||||
@ -192,13 +216,13 @@ void CMarketWindow::createArtifactsSelling(const IMarket * market, const CGHeroI
|
||||
// Create image that copies part of background containing slot MISC_1 into position of slot MISC_5
|
||||
artSlotBack = std::make_shared<CPicture>(background->getSurface(), Rect(20, 187, 47, 47), 0, 0);
|
||||
artSlotBack->moveTo(pos.topLeft() + Point(18, 339));
|
||||
auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero);
|
||||
auto artsSellingMarket = std::make_shared<CArtifactsSelling>(market, hero, getMarketTitle(market->getObjInstanceID(), EMarketMode::ARTIFACT_RESOURCE));
|
||||
artSets.clear();
|
||||
const auto heroArts = artsSellingMarket->getAOHset();
|
||||
heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition){showArifactInfo(*heroArts, artPlace, cursorPosition);};
|
||||
addSet(heroArts);
|
||||
marketWidget = artsSellingMarket;
|
||||
initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);
|
||||
initWidgetInternals(EMarketMode::ARTIFACT_RESOURCE, CGI->generaltexth->zelp[600]);
|
||||
}
|
||||
|
||||
void CMarketWindow::createMarketResources(const IMarket * market, const CGHeroInstance * hero)
|
||||
@ -233,10 +257,10 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
background = createBg(ImagePath::builtin("ALTRART2.bmp"), PLAYER_COLORED);
|
||||
auto altarArtifacts = std::make_shared<CAltarArtifacts>(market, hero);
|
||||
marketWidget = altarArtifacts;
|
||||
auto altarArtifactsStorage = std::make_shared<CAltarArtifacts>(market, hero);
|
||||
marketWidget = altarArtifactsStorage;
|
||||
artSets.clear();
|
||||
const auto heroArts = altarArtifacts->getAOHset();
|
||||
const auto heroArts = altarArtifactsStorage->getAOHset();
|
||||
heroArts->clickPressedCallback = [this, heroArts](const CArtPlace & artPlace, const Point & cursorPosition)
|
||||
{
|
||||
clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false);
|
||||
@ -252,7 +276,7 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns
|
||||
addSet(heroArts);
|
||||
initWidgetInternals(EMarketMode::ARTIFACT_EXP, CGI->generaltexth->zelp[568]);
|
||||
updateExperience();
|
||||
quitButton->addCallback([altarArtifacts](){altarArtifacts->putBackArtifacts();});
|
||||
quitButton->addCallback([altarArtifactsStorage](){altarArtifactsStorage->putBackArtifacts();});
|
||||
}
|
||||
|
||||
void CMarketWindow::createAltarCreatures(const IMarket * market, const CGHeroInstance * hero)
|
||||
|
@ -27,6 +27,7 @@ public:
|
||||
private:
|
||||
void createChangeModeButtons(EMarketMode currentMode, const IMarket * market, const CGHeroInstance * hero);
|
||||
void initWidgetInternals(const EMarketMode mode, const std::pair<std::string, std::string> & quitButtonHelpContainer);
|
||||
std::string getMarketTitle(const ObjectInstanceID marketId, const EMarketMode mode) const;
|
||||
|
||||
void createArtifactsBuying(const IMarket * market, const CGHeroInstance * hero);
|
||||
void createArtifactsSelling(const IMarket * market, const CGHeroInstance * hero);
|
||||
|
@ -819,7 +819,7 @@ void CTransformerWindow::makeDeal()
|
||||
for(auto & elem : items)
|
||||
{
|
||||
if(!elem->left)
|
||||
LOCPLINT->cb->trade(market, EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero);
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::CREATURE_UNDEAD, SlotID(elem->id), {}, {}, hero);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1005,7 +1005,7 @@ void CUniversityWindow::updateSecondarySkills()
|
||||
|
||||
void CUniversityWindow::makeDeal(SecondarySkill skill)
|
||||
{
|
||||
LOCPLINT->cb->trade(market, EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero);
|
||||
LOCPLINT->cb->trade(market->getObjInstanceID(), EMarketMode::RESOURCE_SKILL, GameResID(GameResID::GOLD), skill, 1, hero);
|
||||
}
|
||||
|
||||
CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * owner_, SecondarySkill SKILL, bool available)
|
||||
|
@ -166,7 +166,7 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "ore": 1, "wood": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
|
@ -171,11 +171,11 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "mercury": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
|
||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
|
||||
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1" },
|
||||
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
|
||||
"ship": { "id" : 20, "upgrades" : "shipyard" },
|
||||
|
@ -167,11 +167,11 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "sulfur": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
|
||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
|
||||
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1" },
|
||||
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
|
||||
"special2": {
|
||||
|
@ -165,7 +165,7 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "wood": 1, "ore": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
|
@ -167,7 +167,7 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "mercury": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
|
@ -172,7 +172,7 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "ore": 1, "wood": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
@ -182,7 +182,7 @@
|
||||
"ship": { "id" : 20, "upgrades" : "shipyard" },
|
||||
"special2": { "requires" : [ "mageGuild1" ],
|
||||
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 10, "propagator": "PLAYER_PROPAGATOR" } ] },
|
||||
"special3": { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ] },
|
||||
"special3": { "type" : "creatureTransformer", "requires" : [ "dwellingLvl1" ], "marketModes" : ["creature-undead"] },
|
||||
"grail": { "id" : 26, "mode" : "grail", "produce": { "gold": 5000 },
|
||||
"bonuses": [ { "type": "UNDEAD_RAISE_PERCENTAGE", "val": 20, "propagator": "PLAYER_PROPAGATOR" } ] },
|
||||
|
||||
|
@ -170,7 +170,7 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "crystal": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
|
@ -162,14 +162,14 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce": { "ore": 1, "wood": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
"special1": { "type" : "escapeTunnel", "requires" : [ "fort" ] },
|
||||
"horde1": { "id" : 18, "upgrades" : "dwellingLvl1" },
|
||||
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl1", "requires" : [ "horde1" ], "mode" : "auto" },
|
||||
"special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ] },
|
||||
"special2": { "type" : "freelancersGuild", "requires" : [ "marketplace" ], "marketModes" : ["creature-resource"] },
|
||||
"special3": { "type" : "ballistaYard", "requires" : [ "blacksmith" ] },
|
||||
"special4": {
|
||||
"requires" : [ "fort" ],
|
||||
|
@ -165,11 +165,11 @@
|
||||
"townHall": { },
|
||||
"cityHall": { },
|
||||
"capitol": { },
|
||||
"marketplace": { },
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
"resourceSilo": { "produce" : { "gems": 1 } },
|
||||
"blacksmith": { },
|
||||
|
||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ] },
|
||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
|
||||
"horde1": { "id" : 18, "upgrades" : "dwellingLvl2" },
|
||||
"horde1Upgr": { "id" : 19, "upgrades" : "dwellingUpLvl2", "requires" : [ "horde1" ], "mode" : "auto" },
|
||||
"special2": { "height" : "high", "requires" : [ "fort" ] },
|
||||
|
@ -97,6 +97,11 @@
|
||||
"type" : "array",
|
||||
"description" : "Bonuses that are provided by this building in any town where this building has been built. Only affects town itself (including siege), to propagate effect to player or team please use bonus propagators",
|
||||
"items" : { "$ref" : "bonus.json" }
|
||||
},
|
||||
"marketModes" : {
|
||||
"type" : "array",
|
||||
"enum" : [ "resource-resource", "resource-player", "creature-resource", "resource-artifact", "artifact-resource", "artifact-experience", "creature-experience", "creature-undead", "resource-skill"],
|
||||
"description" : "List of modes available in this market"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -4,6 +4,12 @@ vcmi (1.6.0) jammy; urgency=medium
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Fri, 30 Aug 2024 12:00:00 +0200
|
||||
|
||||
vcmi (1.5.7) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
||||
-- Ivan Savenko <saven.ivan@gmail.com> Mon, 26 Aug 2024 12:00:00 +0200
|
||||
|
||||
vcmi (1.5.6) jammy; urgency=medium
|
||||
|
||||
* New upstream release
|
||||
|
@ -1,7 +1,7 @@
|
||||
[](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.5)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
|
||||
[](https://github.com/vcmi/vcmi/releases/tag/1.5.7)
|
||||
[](https://github.com/vcmi/vcmi/releases)
|
||||
|
||||
# VCMI Project
|
||||
|
@ -171,6 +171,9 @@ These are just a couple of examples of what can be done in VCMI. See vcmi config
|
||||
|
||||
// If set to true, this building will replace all bonuses from base building, leaving only bonuses defined by this building"
|
||||
"upgradeReplacesBonuses" : false,
|
||||
|
||||
// If the building is a market, it requires market mode.
|
||||
"marketModes" : [ "resource-resource", "resource-player" ],
|
||||
}
|
||||
```
|
||||
|
||||
@ -217,7 +220,6 @@ Following HotA buildings can be used as unique building for a town. Functionalit
|
||||
#### Custom buildings
|
||||
In addition to above, it is possible to use same format as [Rewardable](../Map_Objects/Rewardable.md) map objects for town buildings. In order to do that, configuration of a rewardable object must be placed into `configuration` json node in building config.
|
||||
|
||||
```
|
||||
|
||||
### Town Structure node
|
||||
|
||||
@ -248,3 +250,17 @@ In addition to above, it is possible to use same format as [Rewardable](../Map_O
|
||||
"hidden" : false
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Markets in towns
|
||||
Market buildings require list of available [modes](../Map_Objects/Market.md)
|
||||
|
||||
##### Marketplace
|
||||
```jsonc
|
||||
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
|
||||
```
|
||||
|
||||
##### Artifact merchant
|
||||
```jsonc
|
||||
"special1": { "type" : "artifactMerchant", "requires" : [ "marketplace" ], "marketModes" : ["resource-artifact", "artifact-resource"] },
|
||||
```
|
@ -91,6 +91,7 @@
|
||||
<launchable type="desktop-id">vcmilauncher.desktop</launchable>
|
||||
<releases>
|
||||
<release version="1.6.0" date="2024-08-30" type="development"/>
|
||||
<release version="1.5.7" date="2024-08-26" type="stable"/>
|
||||
<release version="1.5.6" date="2024-08-04" type="stable"/>
|
||||
<release version="1.5.5" date="2024-07-17" type="stable"/>
|
||||
<release version="1.5.4" date="2024-07-12" type="stable"/>
|
||||
|
@ -174,6 +174,15 @@ const CGTownInstance* CGameInfoCallback::getTown(ObjectInstanceID objid) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const IMarket * CGameInfoCallback::getMarket(ObjectInstanceID objid) const
|
||||
{
|
||||
const CGObjectInstance * obj = getObj(objid, false);
|
||||
if(obj)
|
||||
return dynamic_cast<const IMarket*>(obj);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CGameInfoCallback::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const
|
||||
{
|
||||
//boost::shared_lock<boost::shared_mutex> lock(*gs->mx);
|
||||
|
@ -48,6 +48,7 @@ class CGHeroInstance;
|
||||
class CGDwelling;
|
||||
class CGTeleport;
|
||||
class CGTownInstance;
|
||||
class IMarket;
|
||||
|
||||
class DLL_LINKAGE IGameInfoCallback : boost::noncopyable
|
||||
{
|
||||
@ -56,7 +57,7 @@ public:
|
||||
|
||||
// //various
|
||||
virtual int getDate(Date mode=Date::DAY) const = 0; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
|
||||
// const StartInfo * getStartInfo(bool beforeRandomization = false)const;
|
||||
virtual const StartInfo * getStartInfo(bool beforeRandomization = false) const = 0;
|
||||
virtual bool isAllowed(SpellID id) const = 0;
|
||||
virtual bool isAllowed(ArtifactID id) const = 0;
|
||||
virtual bool isAllowed(SecondarySkill id) const = 0;
|
||||
@ -143,7 +144,7 @@ protected:
|
||||
public:
|
||||
//various
|
||||
int getDate(Date mode=Date::DAY)const override; //mode=0 - total days in game, mode=1 - day of week, mode=2 - current week, mode=3 - current month
|
||||
virtual const StartInfo * getStartInfo(bool beforeRandomization = false)const;
|
||||
const StartInfo * getStartInfo(bool beforeRandomization = false) const override;
|
||||
bool isAllowed(SpellID id) const override;
|
||||
bool isAllowed(ArtifactID id) const override;
|
||||
bool isAllowed(SecondarySkill id) const override;
|
||||
@ -189,6 +190,7 @@ public:
|
||||
virtual std::vector <const CGObjectInstance * > getFlaggableObjects(int3 pos) const;
|
||||
virtual const CGObjectInstance * getTopObj (int3 pos) const;
|
||||
virtual PlayerColor getOwner(ObjectInstanceID heroID) const;
|
||||
virtual const IMarket * getMarket(ObjectInstanceID objid) const;
|
||||
|
||||
//map
|
||||
virtual int3 guardingCreaturePosition (int3 pos) const;
|
||||
|
@ -13,9 +13,6 @@
|
||||
#include "CStack.h"
|
||||
#include "VCMIDirs.h"
|
||||
|
||||
#include "serializer/BinaryDeserializer.h"
|
||||
#include "serializer/BinarySerializer.h"
|
||||
|
||||
#ifdef STATIC_AI
|
||||
# include "AI/VCAI/VCAI.h"
|
||||
# include "AI/Nullkiller/AIGateway.h"
|
||||
|
@ -53,8 +53,6 @@ class CStack;
|
||||
class CCreature;
|
||||
class CLoadFile;
|
||||
class CSaveFile;
|
||||
class BinaryDeserializer;
|
||||
class BinarySerializer;
|
||||
class BattleStateInfo;
|
||||
struct ArtifactLocation;
|
||||
class BattleStateInfoForRetreat;
|
||||
|
@ -217,6 +217,7 @@ set(lib_MAIN_SRCS
|
||||
serializer/JsonSerializeFormat.cpp
|
||||
serializer/JsonSerializer.cpp
|
||||
serializer/JsonUpdater.cpp
|
||||
serializer/SerializerReflection.cpp
|
||||
|
||||
spells/AbilityCaster.cpp
|
||||
spells/AdventureSpellMechanics.cpp
|
||||
@ -563,12 +564,6 @@ set(lib_MAIN_HEADERS
|
||||
pathfinder/PathfindingRules.h
|
||||
pathfinder/TurnInfo.h
|
||||
|
||||
registerTypes/RegisterTypes.h
|
||||
registerTypes/RegisterTypesClientPacks.h
|
||||
registerTypes/RegisterTypesLobbyPacks.h
|
||||
registerTypes/RegisterTypesMapObjects.h
|
||||
registerTypes/RegisterTypesServerPacks.h
|
||||
|
||||
rewardable/Configuration.h
|
||||
rewardable/Info.h
|
||||
rewardable/Interface.h
|
||||
@ -625,7 +620,9 @@ set(lib_MAIN_HEADERS
|
||||
serializer/JsonUpdater.h
|
||||
serializer/Cast.h
|
||||
serializer/ESerializationVersion.h
|
||||
serializer/RegisterTypes.h
|
||||
serializer/Serializeable.h
|
||||
serializer/SerializerReflection.h
|
||||
|
||||
spells/AbilityCaster.h
|
||||
spells/AdventureSpellMechanics.h
|
||||
|
@ -180,8 +180,7 @@ CGameState * CPrivilegedInfoCallback::gameState()
|
||||
return gs;
|
||||
}
|
||||
|
||||
template<typename Loader>
|
||||
void CPrivilegedInfoCallback::loadCommonState(Loader & in)
|
||||
void CPrivilegedInfoCallback::loadCommonState(CLoadFile & in)
|
||||
{
|
||||
logGlobal->info("Loading lib part of game...");
|
||||
in.checkMagicBytes(SAVEGAME_MAGIC);
|
||||
@ -203,8 +202,7 @@ void CPrivilegedInfoCallback::loadCommonState(Loader & in)
|
||||
in.serializer & gs;
|
||||
}
|
||||
|
||||
template<typename Saver>
|
||||
void CPrivilegedInfoCallback::saveCommonState(Saver & out) const
|
||||
void CPrivilegedInfoCallback::saveCommonState(CSaveFile & out) const
|
||||
{
|
||||
ActiveModsInSaveList activeMods;
|
||||
|
||||
@ -220,10 +218,6 @@ void CPrivilegedInfoCallback::saveCommonState(Saver & out) const
|
||||
out.serializer & gs;
|
||||
}
|
||||
|
||||
// hardly memory usage for `-gdwarf-4` flag
|
||||
template DLL_LINKAGE void CPrivilegedInfoCallback::loadCommonState<CLoadFile>(CLoadFile &);
|
||||
template DLL_LINKAGE void CPrivilegedInfoCallback::saveCommonState<CSaveFile>(CSaveFile &) const;
|
||||
|
||||
TerrainTile * CNonConstInfoCallback::getTile(const int3 & pos)
|
||||
{
|
||||
if(!gs->map->isInTheMap(pos))
|
||||
@ -287,18 +281,16 @@ CArtifactSet * CNonConstInfoCallback::getArtSet(const ArtifactLocation & loc)
|
||||
return hero;
|
||||
}
|
||||
}
|
||||
else if(auto market = getMarket(loc.artHolder))
|
||||
{
|
||||
if(auto artSet = market->getArtifactsStorage())
|
||||
return artSet.get();
|
||||
}
|
||||
else if(auto army = getArmyInstance(loc.artHolder))
|
||||
{
|
||||
return army->getStackPtr(loc.creature.value());
|
||||
}
|
||||
else if(auto market = dynamic_cast<CGArtifactsAltar*>(getObjInstance(loc.artHolder)))
|
||||
{
|
||||
return market;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool IGameCallback::isVisitCoveredByAnotherQuery(const CGObjectInstance *obj, const CGHeroInstance *hero)
|
||||
|
@ -31,6 +31,8 @@ struct BankConfig;
|
||||
class CCreatureSet;
|
||||
class CStackBasicDescriptor;
|
||||
class CGCreature;
|
||||
class CSaveFile;
|
||||
class CLoadFile;
|
||||
enum class EOpenWindowMode : uint8_t;
|
||||
|
||||
namespace spells
|
||||
@ -74,11 +76,8 @@ public:
|
||||
void pickAllowedArtsSet(std::vector<const CArtifact *> & out, vstd::RNG & rand);
|
||||
void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
|
||||
|
||||
template<typename Saver>
|
||||
void saveCommonState(Saver &out) const; //stores GS and VLC
|
||||
|
||||
template<typename Loader>
|
||||
void loadCommonState(Loader &in); //loads GS and VLC
|
||||
void saveCommonState(CSaveFile &out) const; //stores GS and VLC
|
||||
void loadCommonState(CLoadFile &in); //loads GS and VLC
|
||||
};
|
||||
|
||||
class DLL_LINKAGE IGameEventCallback
|
||||
|
@ -109,7 +109,7 @@ public:
|
||||
|
||||
virtual void showPuzzleMap(){};
|
||||
virtual void viewWorldMap(){};
|
||||
virtual void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){};
|
||||
virtual void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor, QueryID queryID){};
|
||||
virtual void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor, QueryID queryID){};
|
||||
virtual void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor){};
|
||||
virtual void showThievesGuildWindow (const CGObjectInstance * obj){};
|
||||
|
@ -18,9 +18,19 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
bool AccessibilityInfo::tileAccessibleWithGate(BattleHex tile, BattleSide side) const
|
||||
{
|
||||
//at(otherHex) != EAccessibility::ACCESSIBLE && (at(otherHex) != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
if(at(tile) != EAccessibility::ACCESSIBLE)
|
||||
if(at(tile) != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
auto accessibility = at(tile);
|
||||
|
||||
if(accessibility == EAccessibility::ALIVE_STACK)
|
||||
{
|
||||
auto destructible = destructibleEnemyTurns.find(tile);
|
||||
|
||||
return destructible != destructibleEnemyTurns.end();
|
||||
}
|
||||
|
||||
if(accessibility != EAccessibility::ACCESSIBLE)
|
||||
if(accessibility != EAccessibility::GATE || side != BattleSide::DEFENDER)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,8 @@ using TAccessibilityArray = std::array<EAccessibility, GameConstants::BFIELD_SIZ
|
||||
|
||||
struct DLL_LINKAGE AccessibilityInfo : TAccessibilityArray
|
||||
{
|
||||
std::map<BattleHex, ui8> destructibleEnemyTurns;
|
||||
|
||||
public:
|
||||
bool accessible(BattleHex tile, const battle::Unit * stack) const; //checks for both tiles if stack is double wide
|
||||
bool accessible(BattleHex tile, bool doubleWide, BattleSide side) const; //checks for both tiles if stack is double wide
|
||||
|
@ -1051,17 +1051,30 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
|
||||
if(isInObstacle(curHex, obstacles, checkParams))
|
||||
continue;
|
||||
|
||||
const int costToNeighbour = ret.distances[curHex.hex] + 1;
|
||||
const int costToNeighbour = ret.distances.at(curHex.hex) + 1;
|
||||
|
||||
for(BattleHex neighbour : BattleHex::neighbouringTilesCache[curHex.hex])
|
||||
{
|
||||
if(neighbour.isValid())
|
||||
{
|
||||
auto additionalCost = 0;
|
||||
|
||||
if(params.bypassEnemyStacks)
|
||||
{
|
||||
auto enemyToBypass = params.destructibleEnemyTurns.find(neighbour);
|
||||
|
||||
if(enemyToBypass != params.destructibleEnemyTurns.end())
|
||||
{
|
||||
additionalCost = enemyToBypass->second;
|
||||
}
|
||||
}
|
||||
|
||||
const int costFoundSoFar = ret.distances[neighbour.hex];
|
||||
|
||||
if(accessibleCache[neighbour.hex] && costToNeighbour < costFoundSoFar)
|
||||
if(accessibleCache[neighbour.hex] && costToNeighbour + additionalCost < costFoundSoFar)
|
||||
{
|
||||
hexq.push(neighbour);
|
||||
ret.distances[neighbour.hex] = costToNeighbour;
|
||||
ret.distances[neighbour.hex] = costToNeighbour + additionalCost;
|
||||
ret.predecessors[neighbour.hex] = curHex;
|
||||
}
|
||||
}
|
||||
@ -1236,7 +1249,13 @@ ReachabilityInfo CBattleInfoCallback::getReachability(const ReachabilityInfo::Pa
|
||||
if(params.flying)
|
||||
return getFlyingReachability(params);
|
||||
else
|
||||
return makeBFS(getAccessibility(params.knownAccessible), params);
|
||||
{
|
||||
auto accessibility = getAccessibility(params.knownAccessible);
|
||||
|
||||
accessibility.destructibleEnemyTurns = params.destructibleEnemyTurns;
|
||||
|
||||
return makeBFS(accessibility, params);
|
||||
}
|
||||
}
|
||||
|
||||
ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityInfo::Parameters ¶ms) const
|
||||
|
@ -900,9 +900,9 @@ CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBear
|
||||
{
|
||||
}
|
||||
|
||||
TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const CBonusSystemNode * root, const std::string & cachingStr) const
|
||||
TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const std::string & cachingStr) const
|
||||
{
|
||||
return bonus->getAllBonuses(selector, limit, root, cachingStr);
|
||||
return bonus->getAllBonuses(selector, limit, cachingStr);
|
||||
}
|
||||
|
||||
int64_t CUnitStateDetached::getTreeVersion() const
|
||||
|
@ -282,7 +282,7 @@ public:
|
||||
explicit CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_);
|
||||
|
||||
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;
|
||||
|
||||
|
@ -29,7 +29,9 @@ struct DLL_LINKAGE ReachabilityInfo
|
||||
bool doubleWide = false;
|
||||
bool flying = false;
|
||||
bool ignoreKnownAccessible = false; //Ignore obstacles if it is in accessible hexes
|
||||
bool bypassEnemyStacks = false; // in case of true will count amount of turns needed to kill enemy and thus move forward
|
||||
std::vector<BattleHex> knownAccessible; //hexes that will be treated as accessible, even if they're occupied by stack (by default - tiles occupied by stack we do reachability for, so it doesn't block itself)
|
||||
std::map<BattleHex, ui8> destructibleEnemyTurns; // hom many turns it is needed to kill enemy on specific hex
|
||||
|
||||
BattleHex startPosition; //assumed position of stack
|
||||
BattleSide perspective = BattleSide::ALL_KNOWING; //some obstacles (eg. quicksands) may be invisible for some side
|
||||
|
@ -107,10 +107,9 @@ void CBonusSystemNode::getAllBonusesRec(BonusList &out, const CSelector & select
|
||||
}
|
||||
}
|
||||
|
||||
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root, const std::string &cachingStr) const
|
||||
TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
|
||||
{
|
||||
bool limitOnUs = (!root || root == this); //caching won't work when we want to limit bonuses against an external node
|
||||
if (CBonusSystemNode::cachingEnabled && limitOnUs)
|
||||
if (CBonusSystemNode::cachingEnabled)
|
||||
{
|
||||
// Exclusive access for one thread
|
||||
boost::lock_guard<boost::mutex> lock(sync);
|
||||
@ -157,11 +156,11 @@ TConstBonusListPtr CBonusSystemNode::getAllBonuses(const CSelector &selector, co
|
||||
}
|
||||
else
|
||||
{
|
||||
return getAllBonusesWithoutCaching(selector, limit, root);
|
||||
return getAllBonusesWithoutCaching(selector, limit);
|
||||
}
|
||||
}
|
||||
|
||||
TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root) const
|
||||
TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const
|
||||
{
|
||||
auto ret = std::make_shared<BonusList>();
|
||||
|
||||
@ -169,29 +168,7 @@ TConstBonusListPtr CBonusSystemNode::getAllBonusesWithoutCaching(const CSelector
|
||||
BonusList beforeLimiting;
|
||||
BonusList afterLimiting;
|
||||
getAllBonusesRec(beforeLimiting, selector);
|
||||
|
||||
if(!root || root == this)
|
||||
{
|
||||
limitBonuses(beforeLimiting, afterLimiting);
|
||||
}
|
||||
else if(root)
|
||||
{
|
||||
//We want to limit our query against an external node. We get all its bonuses,
|
||||
// add the ones we're considering and see if they're cut out by limiters
|
||||
BonusList rootBonuses;
|
||||
BonusList limitedRootBonuses;
|
||||
getAllBonusesRec(rootBonuses, selector);
|
||||
|
||||
for(const auto & b : beforeLimiting)
|
||||
rootBonuses.push_back(b);
|
||||
|
||||
root->limitBonuses(rootBonuses, limitedRootBonuses);
|
||||
|
||||
for(const auto & b : beforeLimiting)
|
||||
if(vstd::contains(limitedRootBonuses, b))
|
||||
afterLimiting.push_back(b);
|
||||
|
||||
}
|
||||
limitBonuses(beforeLimiting, afterLimiting);
|
||||
afterLimiting.getBonuses(*ret, selector, limit);
|
||||
ret->stackBonuses();
|
||||
return ret;
|
||||
|
@ -53,7 +53,7 @@ private:
|
||||
mutable boost::mutex sync;
|
||||
|
||||
void getAllBonusesRec(BonusList &out, const CSelector & selector) const;
|
||||
TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr) const;
|
||||
TConstBonusListPtr getAllBonusesWithoutCaching(const CSelector &selector, const CSelector &limit) const;
|
||||
std::shared_ptr<Bonus> getUpdatedBonus(const std::shared_ptr<Bonus> & b, const TUpdaterPtr & updater) const;
|
||||
|
||||
void getRedParents(TCNodes &out) const; //retrieves list of red parent nodes (nodes bonuses propagate from)
|
||||
@ -86,7 +86,7 @@ public:
|
||||
|
||||
void limitBonuses(const BonusList &allBonuses, BonusList &out) const; //out will bo populed with bonuses that are not limited here
|
||||
TBonusListPtr limitBonuses(const BonusList &allBonuses) const; //same as above, returns out by val for convenience
|
||||
TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const override;
|
||||
TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const override;
|
||||
void getParents(TCNodes &out) const; //retrieves list of parent nodes (nodes to inherit bonuses from),
|
||||
|
||||
/// Returns first bonus matching selector
|
||||
|
@ -17,7 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
int IBonusBearer::valOfBonuses(const CSelector &selector, const std::string &cachingStr) const
|
||||
{
|
||||
TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, nullptr, cachingStr);
|
||||
TConstBonusListPtr hlp = getAllBonuses(selector, nullptr, cachingStr);
|
||||
return hlp->totalValue();
|
||||
}
|
||||
|
||||
@ -34,12 +34,12 @@ bool IBonusBearer::hasBonus(const CSelector &selector, const CSelector &limit, c
|
||||
|
||||
TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const std::string &cachingStr) const
|
||||
{
|
||||
return getAllBonuses(selector, nullptr, nullptr, cachingStr);
|
||||
return getAllBonuses(selector, nullptr, cachingStr);
|
||||
}
|
||||
|
||||
TConstBonusListPtr IBonusBearer::getBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr) const
|
||||
{
|
||||
return getAllBonuses(selector, limit, nullptr, cachingStr);
|
||||
return getAllBonuses(selector, limit, cachingStr);
|
||||
}
|
||||
|
||||
int IBonusBearer::valOfBonuses(BonusType type) const
|
||||
|
@ -22,7 +22,7 @@ public:
|
||||
//interface
|
||||
IBonusBearer() = default;
|
||||
virtual ~IBonusBearer() = default;
|
||||
virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const CBonusSystemNode *root = nullptr, const std::string &cachingStr = "") const = 0;
|
||||
virtual TConstBonusListPtr getAllBonuses(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const = 0;
|
||||
int valOfBonuses(const CSelector &selector, const std::string &cachingStr = "") const;
|
||||
bool hasBonus(const CSelector &selector, const std::string &cachingStr = "") const;
|
||||
bool hasBonus(const CSelector &selector, const CSelector &limit, const std::string &cachingStr = "") const;
|
||||
|
@ -352,6 +352,14 @@ public:
|
||||
return (dwelling - DWELL_FIRST) / (GameConstants::CREATURES_PER_TOWN - 1);
|
||||
}
|
||||
|
||||
static void advanceDwelling(BuildingIDBase & dwelling)
|
||||
{
|
||||
if(dwelling != BuildingIDBase::DWELL_LVL_8)
|
||||
dwelling.advance(GameConstants::CREATURES_PER_TOWN - 1);
|
||||
else
|
||||
dwelling.advance(1);
|
||||
}
|
||||
|
||||
bool IsSpecialOrGrail() const
|
||||
{
|
||||
return num == SPECIAL_1 || num == SPECIAL_2 || num == SPECIAL_3 || num == SPECIAL_4 || num == GRAIL;
|
||||
|
@ -34,6 +34,7 @@ public:
|
||||
TResources resources;
|
||||
TResources produce;
|
||||
TRequired requirements;
|
||||
std::set<EMarketMode> marketModes;
|
||||
|
||||
BuildingID bid; //structure ID
|
||||
BuildingID upgrade; /// indicates that building "upgrade" can be improved by this, -1 = empty
|
||||
@ -85,7 +86,7 @@ public:
|
||||
STRONG_INLINE
|
||||
bool IsTradeBuilding() const
|
||||
{
|
||||
return bid == BuildingID::MARKETPLACE || subId == BuildingSubID::ARTIFACT_MERCHANT || subId == BuildingSubID::FREELANCERS_GUILD;
|
||||
return !marketModes.empty();
|
||||
}
|
||||
|
||||
void addNewBonus(const std::shared_ptr<Bonus> & b, BonusList & bonusList) const;
|
||||
|
@ -342,6 +342,11 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
|
||||
ret->upgrade = BuildingID::NONE;
|
||||
|
||||
ret->town->buildings[ret->bid] = ret;
|
||||
for(const auto & element : source["marketModes"].Vector())
|
||||
{
|
||||
if(MappedKeys::MARKET_NAMES_TO_TYPES.count(element.String()))
|
||||
ret->marketModes.insert(MappedKeys::MARKET_NAMES_TO_TYPES.at(element.String()));
|
||||
}
|
||||
|
||||
registerObject(source.getModScope(), ret->town->getBuildingScope(), ret->identifier, ret->bid.getNum());
|
||||
}
|
||||
|
@ -45,12 +45,11 @@
|
||||
#include "../mapping/CMapService.h"
|
||||
#include "../modding/IdentifierStorage.h"
|
||||
#include "../modding/ModScope.h"
|
||||
#include "../networkPacks/NetPacksBase.h"
|
||||
#include "../pathfinder/CPathfinder.h"
|
||||
#include "../pathfinder/PathfinderOptions.h"
|
||||
#include "../registerTypes/RegisterTypesClientPacks.h"
|
||||
#include "../rmg/CMapGenerator.h"
|
||||
#include "../serializer/CMemorySerializer.h"
|
||||
#include "../serializer/CTypeList.h"
|
||||
#include "../spells/CSpellHandler.h"
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
@ -59,29 +58,6 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
boost::shared_mutex CGameState::mutex;
|
||||
|
||||
template <typename T> class CApplyOnGS;
|
||||
|
||||
class CBaseForGSApply
|
||||
{
|
||||
public:
|
||||
virtual void applyOnGS(CGameState *gs, CPack * pack) const =0;
|
||||
virtual ~CBaseForGSApply() = default;
|
||||
template<typename U> static CBaseForGSApply *getApplier(const U * t=nullptr)
|
||||
{
|
||||
return new CApplyOnGS<U>();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> class CApplyOnGS : public CBaseForGSApply
|
||||
{
|
||||
public:
|
||||
void applyOnGS(CGameState *gs, CPack * pack) const override
|
||||
{
|
||||
T *ptr = static_cast<T*>(pack);
|
||||
ptr->applyGs(gs);
|
||||
}
|
||||
};
|
||||
|
||||
HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
|
||||
{
|
||||
const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
|
||||
@ -165,8 +141,6 @@ CGameState::CGameState()
|
||||
{
|
||||
gs = this;
|
||||
heroesPool = std::make_unique<TavernHeroesPool>();
|
||||
applier = std::make_shared<CApplier<CBaseForGSApply>>();
|
||||
registerTypesClientPacks(*applier);
|
||||
globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
|
||||
}
|
||||
|
||||
@ -303,6 +277,27 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
|
||||
std::unique_ptr<CMap> randomMap = mapGenerator.generate();
|
||||
progressTracking.exclude(mapGenerator);
|
||||
|
||||
// Update starting options
|
||||
for(int i = 0; i < randomMap->players.size(); ++i)
|
||||
{
|
||||
const auto & playerInfo = randomMap->players[i];
|
||||
if(playerInfo.canAnyonePlay())
|
||||
{
|
||||
PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)];
|
||||
playerSettings.compOnly = !playerInfo.canHumanPlay;
|
||||
playerSettings.castle = playerInfo.defaultCastle();
|
||||
if(playerSettings.isControlledByAI() && playerSettings.name.empty())
|
||||
{
|
||||
playerSettings.name = VLC->generaltexth->allTexts[468];
|
||||
}
|
||||
playerSettings.color = PlayerColor(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
scenarioOps->playerInfos.erase(PlayerColor(i));
|
||||
}
|
||||
}
|
||||
|
||||
if(allowSavingRandomMap)
|
||||
{
|
||||
try
|
||||
@ -332,26 +327,6 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
|
||||
}
|
||||
|
||||
map = randomMap.release();
|
||||
// Update starting options
|
||||
for(int i = 0; i < map->players.size(); ++i)
|
||||
{
|
||||
const auto & playerInfo = map->players[i];
|
||||
if(playerInfo.canAnyonePlay())
|
||||
{
|
||||
PlayerSettings & playerSettings = scenarioOps->playerInfos[PlayerColor(i)];
|
||||
playerSettings.compOnly = !playerInfo.canHumanPlay;
|
||||
playerSettings.castle = playerInfo.defaultCastle();
|
||||
if(playerSettings.isControlledByAI() && playerSettings.name.empty())
|
||||
{
|
||||
playerSettings.name = VLC->generaltexth->allTexts[468];
|
||||
}
|
||||
playerSettings.color = PlayerColor(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
scenarioOps->playerInfos.erase(PlayerColor(i));
|
||||
}
|
||||
}
|
||||
|
||||
logGlobal->info("Generated random map in %i ms.", sw.getDiff());
|
||||
}
|
||||
@ -807,12 +782,12 @@ void CGameState::initTowns()
|
||||
constexpr std::array hordes = { BuildingID::HORDE_PLACEHOLDER1, BuildingID::HORDE_PLACEHOLDER2, BuildingID::HORDE_PLACEHOLDER3, BuildingID::HORDE_PLACEHOLDER4, BuildingID::HORDE_PLACEHOLDER5, BuildingID::HORDE_PLACEHOLDER6, BuildingID::HORDE_PLACEHOLDER7, BuildingID::HORDE_PLACEHOLDER8 };
|
||||
|
||||
//init buildings
|
||||
if(vstd::contains(vti->builtBuildings, BuildingID::DEFAULT)) //give standard set of buildings
|
||||
if(vti->hasBuilt(BuildingID::DEFAULT)) //give standard set of buildings
|
||||
{
|
||||
vti->builtBuildings.erase(BuildingID::DEFAULT);
|
||||
vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
|
||||
vti->removeBuilding(BuildingID::DEFAULT);
|
||||
vti->addBuilding(BuildingID::VILLAGE_HALL);
|
||||
if(vti->tempOwner != PlayerColor::NEUTRAL)
|
||||
vti->builtBuildings.insert(BuildingID::TAVERN);
|
||||
vti->addBuilding(BuildingID::TAVERN);
|
||||
|
||||
auto definesBuildingsChances = VLC->settings()->getVector(EGameSettings::TOWNS_STARTING_DWELLING_CHANCES);
|
||||
|
||||
@ -820,48 +795,49 @@ void CGameState::initTowns()
|
||||
{
|
||||
if((getRandomGenerator().nextInt(1,100) <= definesBuildingsChances[i]))
|
||||
{
|
||||
vti->builtBuildings.insert(basicDwellings[i]);
|
||||
vti->addBuilding(basicDwellings[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// village hall must always exist
|
||||
vti->builtBuildings.insert(BuildingID::VILLAGE_HALL);
|
||||
vti->addBuilding(BuildingID::VILLAGE_HALL);
|
||||
|
||||
//init hordes
|
||||
for (int i = 0; i < vti->town->creatures.size(); i++)
|
||||
{
|
||||
if (vstd::contains(vti->builtBuildings, hordes[i])) //if we have horde for this level
|
||||
if(vti->hasBuilt(hordes[i])) //if we have horde for this level
|
||||
{
|
||||
vti->builtBuildings.erase(hordes[i]);//remove old ID
|
||||
vti->removeBuilding(hordes[i]);//remove old ID
|
||||
if (vti->getTown()->hordeLvl.at(0) == i)//if town first horde is this one
|
||||
{
|
||||
vti->builtBuildings.insert(BuildingID::HORDE_1);//add it
|
||||
vti->addBuilding(BuildingID::HORDE_1);//add it
|
||||
//if we have upgraded dwelling as well
|
||||
if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
|
||||
vti->builtBuildings.insert(BuildingID::HORDE_1_UPGR);//add it as well
|
||||
if(vti->hasBuilt(upgradedDwellings[i]))
|
||||
vti->addBuilding(BuildingID::HORDE_1_UPGR);//add it as well
|
||||
}
|
||||
if (vti->getTown()->hordeLvl.at(1) == i)//if town second horde is this one
|
||||
{
|
||||
vti->builtBuildings.insert(BuildingID::HORDE_2);
|
||||
if (vstd::contains(vti->builtBuildings, upgradedDwellings[i]))
|
||||
vti->builtBuildings.insert(BuildingID::HORDE_2_UPGR);
|
||||
vti->addBuilding(BuildingID::HORDE_2);
|
||||
if(vti->hasBuilt(upgradedDwellings[i]))
|
||||
vti->addBuilding(BuildingID::HORDE_2_UPGR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#1444 - remove entries that don't have buildings defined (like some unused extra town hall buildings)
|
||||
//But DO NOT remove horde placeholders before they are replaced
|
||||
vstd::erase_if(vti->builtBuildings, [vti](const BuildingID & bid)
|
||||
{
|
||||
return !vti->getTown()->buildings.count(bid) || !vti->getTown()->buildings.at(bid);
|
||||
});
|
||||
for(const auto & building : vti->getBuildings())
|
||||
{
|
||||
if(!vti->getTown()->buildings.count(building) || !vti->getTown()->buildings.at(building))
|
||||
vti->removeBuilding(building);
|
||||
}
|
||||
|
||||
if (vstd::contains(vti->builtBuildings, BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED)
|
||||
vti->builtBuildings.erase(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour)
|
||||
if(vti->hasBuilt(BuildingID::SHIPYARD) && vti->shipyardStatus()==IBoatGenerator::TILE_BLOCKED)
|
||||
vti->removeBuilding(BuildingID::SHIPYARD);//if we have harbor without water - erase it (this is H3 behaviour)
|
||||
|
||||
//Early check for #1444-like problems
|
||||
for([[maybe_unused]] const auto & building : vti->builtBuildings)
|
||||
for([[maybe_unused]] const auto & building : vti->getBuildings())
|
||||
{
|
||||
assert(vti->getTown()->buildings.at(building) != nullptr);
|
||||
}
|
||||
@ -1144,10 +1120,9 @@ PlayerRelations CGameState::getPlayerRelations( PlayerColor color1, PlayerColor
|
||||
return PlayerRelations::ENEMIES;
|
||||
}
|
||||
|
||||
void CGameState::apply(CPack *pack)
|
||||
void CGameState::apply(CPackForClient *pack)
|
||||
{
|
||||
ui16 typ = CTypeList::getInstance().getTypeID(pack);
|
||||
applier->getApplier(typ)->applyOnGS(this, pack);
|
||||
pack->applyGs(this);
|
||||
}
|
||||
|
||||
void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
|
||||
|
@ -38,9 +38,6 @@ class TavernHeroesPool;
|
||||
struct SThievesGuildInfo;
|
||||
class CRandomGenerator;
|
||||
|
||||
template<typename T> class CApplier;
|
||||
class CBaseForGSApply;
|
||||
|
||||
struct UpgradeInfo
|
||||
{
|
||||
CreatureID oldID; //creature to be upgraded
|
||||
@ -101,7 +98,7 @@ public:
|
||||
/// picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
|
||||
HeroTypeID pickNextHeroType(const PlayerColor & owner);
|
||||
|
||||
void apply(CPack *pack);
|
||||
void apply(CPackForClient *pack);
|
||||
BattleField battleGetBattlefieldType(int3 tile, vstd::RNG & rand);
|
||||
|
||||
void fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const override;
|
||||
@ -215,7 +212,6 @@ private:
|
||||
UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const;
|
||||
|
||||
// ---- data -----
|
||||
std::shared_ptr<CApplier<CBaseForGSApply>> applier;
|
||||
Services * services;
|
||||
|
||||
/// Pointer to campaign state manager. Nullptr for single scenarios
|
||||
|
@ -656,7 +656,7 @@ void CGameStateCampaign::initTowns()
|
||||
if(gameState->scenarioOps->campState->formatVCMI())
|
||||
newBuilding = BuildingID(chosenBonus->info1);
|
||||
else
|
||||
newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->builtBuildings);
|
||||
newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->getBuildings());
|
||||
|
||||
// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
|
||||
while(true)
|
||||
@ -664,10 +664,10 @@ void CGameStateCampaign::initTowns()
|
||||
if (newBuilding == BuildingID::NONE)
|
||||
break;
|
||||
|
||||
if (town->builtBuildings.count(newBuilding) != 0)
|
||||
if(town->hasBuilt(newBuilding))
|
||||
break;
|
||||
|
||||
town->builtBuildings.insert(newBuilding);
|
||||
town->addBuilding(newBuilding);
|
||||
|
||||
auto building = town->town->buildings.at(newBuilding);
|
||||
newBuilding = building->upgrade;
|
||||
|
@ -372,7 +372,7 @@ float Statistic::getTownBuiltRatio(const PlayerState * ps)
|
||||
|
||||
for(const auto & t : ps->towns)
|
||||
{
|
||||
built += t->builtBuildings.size();
|
||||
built += t->getBuildings().size();
|
||||
for(const auto & b : t->town->buildings)
|
||||
if(!t->forbiddenBuildings.count(b.first))
|
||||
total += 1;
|
||||
|
@ -33,7 +33,7 @@ HighScoreParameter HighScore::prepareHighScores(const CGameState * gs, PlayerCol
|
||||
if(h->hasArt(ArtifactID::GRAIL))
|
||||
param.hasGrail = true;
|
||||
for(const CGTownInstance * t : playerState->towns)
|
||||
if(t->builtBuildings.count(BuildingID::GRAIL))
|
||||
if(t->hasBuilt(BuildingID::GRAIL))
|
||||
param.hasGrail = true;
|
||||
param.allEnemiesDefeated = true;
|
||||
for (PlayerColor otherPlayer(0); otherPlayer < PlayerColor::PLAYER_LIMIT; ++otherPlayer)
|
||||
|
@ -120,7 +120,7 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
|
||||
legacyTemplates.insert(std::make_pair(key, tmpl));
|
||||
}
|
||||
|
||||
objects.resize(256);
|
||||
mapObjectTypes.resize(256);
|
||||
|
||||
std::vector<JsonNode> ret(dataSize);// create storage for 256 objects
|
||||
assert(dataSize == 256);
|
||||
@ -162,39 +162,39 @@ std::vector<JsonNode> CObjectClassesHandler::loadLegacyData()
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj)
|
||||
void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject)
|
||||
{
|
||||
auto object = loadSubObjectFromJson(scope, identifier, entry, obj, obj->objects.size());
|
||||
auto subObject = loadSubObjectFromJson(scope, identifier, entry, baseObject, baseObject->objectTypeHandlers.size());
|
||||
|
||||
assert(object);
|
||||
obj->objects.push_back(object);
|
||||
assert(subObject);
|
||||
baseObject->objectTypeHandlers.push_back(subObject);
|
||||
|
||||
registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype);
|
||||
registerObject(scope, baseObject->getJsonKey(), subObject->getSubTypeName(), subObject->subtype);
|
||||
for(const auto & compatID : entry["compatibilityIdentifiers"].Vector())
|
||||
registerObject(scope, obj->getJsonKey(), compatID.String(), object->subtype);
|
||||
registerObject(scope, baseObject->getJsonKey(), compatID.String(), subObject->subtype);
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index)
|
||||
void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject, size_t index)
|
||||
{
|
||||
auto object = loadSubObjectFromJson(scope, identifier, entry, obj, index);
|
||||
auto subObject = loadSubObjectFromJson(scope, identifier, entry, baseObject, index);
|
||||
|
||||
assert(object);
|
||||
if (obj->objects.at(index) != nullptr)
|
||||
assert(subObject);
|
||||
if (baseObject->objectTypeHandlers.at(index) != nullptr)
|
||||
throw std::runtime_error("Attempt to load already loaded object:" + identifier);
|
||||
|
||||
obj->objects.at(index) = object;
|
||||
baseObject->objectTypeHandlers.at(index) = subObject;
|
||||
|
||||
registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype);
|
||||
registerObject(scope, baseObject->getJsonKey(), subObject->getSubTypeName(), subObject->subtype);
|
||||
for(const auto & compatID : entry["compatibilityIdentifiers"].Vector())
|
||||
registerObject(scope, obj->getJsonKey(), compatID.String(), object->subtype);
|
||||
registerObject(scope, baseObject->getJsonKey(), compatID.String(), subObject->subtype);
|
||||
}
|
||||
|
||||
TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index)
|
||||
TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * baseObject, size_t index)
|
||||
{
|
||||
assert(identifier.find(':') == std::string::npos);
|
||||
assert(!scope.empty());
|
||||
|
||||
std::string handler = obj->handlerName;
|
||||
std::string handler = baseObject->handlerName;
|
||||
if(!handlerConstructors.count(handler))
|
||||
{
|
||||
logMod->error("Handler with name %s was not found!", handler);
|
||||
@ -206,10 +206,10 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
|
||||
auto createdObject = handlerConstructors.at(handler)();
|
||||
|
||||
createdObject->modScope = scope;
|
||||
createdObject->typeName = obj->identifier;
|
||||
createdObject->typeName = baseObject->identifier;
|
||||
createdObject->subTypeName = identifier;
|
||||
|
||||
createdObject->type = obj->id;
|
||||
createdObject->type = baseObject->id;
|
||||
createdObject->subtype = index;
|
||||
createdObject->init(entry);
|
||||
|
||||
@ -223,7 +223,7 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
|
||||
}
|
||||
}
|
||||
|
||||
auto range = legacyTemplates.equal_range(std::make_pair(obj->id, index));
|
||||
auto range = legacyTemplates.equal_range(std::make_pair(baseObject->id, index));
|
||||
for (auto & templ : boost::make_iterator_range(range.first, range.second))
|
||||
{
|
||||
if (staticObject)
|
||||
@ -238,7 +238,7 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin
|
||||
}
|
||||
legacyTemplates.erase(range.first, range.second);
|
||||
|
||||
logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->getJsonKey(), obj->id, identifier, index);
|
||||
logGlobal->debug("Loaded object %s(%d)::%s(%d)", baseObject->getJsonKey(), baseObject->id, identifier, index);
|
||||
|
||||
return createdObject;
|
||||
}
|
||||
@ -263,17 +263,17 @@ std::string ObjectClass::getNameTranslated() const
|
||||
|
||||
std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index)
|
||||
{
|
||||
auto obj = std::make_unique<ObjectClass>();
|
||||
auto newObject = std::make_unique<ObjectClass>();
|
||||
|
||||
obj->modScope = scope;
|
||||
obj->identifier = name;
|
||||
obj->handlerName = json["handler"].String();
|
||||
obj->base = json["base"];
|
||||
obj->id = index;
|
||||
newObject->modScope = scope;
|
||||
newObject->identifier = name;
|
||||
newObject->handlerName = json["handler"].String();
|
||||
newObject->base = json["base"];
|
||||
newObject->id = index;
|
||||
|
||||
VLC->generaltexth->registerString(scope, obj->getNameTextID(), json["name"].String());
|
||||
VLC->generaltexth->registerString(scope, newObject->getNameTextID(), json["name"].String());
|
||||
|
||||
obj->objects.resize(json["lastReservedIndex"].Float() + 1);
|
||||
newObject->objectTypeHandlers.resize(json["lastReservedIndex"].Float() + 1);
|
||||
|
||||
for (auto subData : json["types"].Struct())
|
||||
{
|
||||
@ -284,68 +284,71 @@ std::unique_ptr<ObjectClass> CObjectClassesHandler::loadFromJson(const std::stri
|
||||
if ( subMeta == "core")
|
||||
{
|
||||
size_t subIndex = subData.second["index"].Integer();
|
||||
loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get(), subIndex);
|
||||
loadSubObject(subData.second.getModScope(), subData.first, subData.second, newObject.get(), subIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
logMod->error("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first );
|
||||
loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
|
||||
loadSubObject(subData.second.getModScope(), subData.first, subData.second, newObject.get());
|
||||
}
|
||||
}
|
||||
else
|
||||
loadSubObject(subData.second.getModScope(), subData.first, subData.second, obj.get());
|
||||
loadSubObject(subData.second.getModScope(), subData.first, subData.second, newObject.get());
|
||||
}
|
||||
|
||||
if (obj->id == MapObjectID::MONOLITH_TWO_WAY)
|
||||
generateExtraMonolithsForRMG(obj.get());
|
||||
if (newObject->id == MapObjectID::MONOLITH_TWO_WAY)
|
||||
generateExtraMonolithsForRMG(newObject.get());
|
||||
|
||||
return obj;
|
||||
return newObject;
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
|
||||
{
|
||||
objects.push_back(loadFromJson(scope, data, name, objects.size()));
|
||||
mapObjectTypes.push_back(loadFromJson(scope, data, name, mapObjectTypes.size()));
|
||||
|
||||
VLC->identifiersHandler->registerObject(scope, "object", name, objects.back()->id);
|
||||
VLC->identifiersHandler->registerObject(scope, "object", name, mapObjectTypes.back()->id);
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
|
||||
{
|
||||
assert(objects.at(index) == nullptr); // ensure that this id was not loaded before
|
||||
assert(mapObjectTypes.at(index) == nullptr); // ensure that this id was not loaded before
|
||||
|
||||
objects.at(index) = loadFromJson(scope, data, name, index);
|
||||
VLC->identifiersHandler->registerObject(scope, "object", name, objects.at(index)->id);
|
||||
mapObjectTypes.at(index) = loadFromJson(scope, data, name, index);
|
||||
VLC->identifiersHandler->registerObject(scope, "object", name, mapObjectTypes.at(index)->id);
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID)
|
||||
{
|
||||
config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL
|
||||
assert(objects.at(ID.getNum()));
|
||||
|
||||
if ( subID.getNum() >= objects.at(ID.getNum())->objects.size())
|
||||
objects.at(ID.getNum())->objects.resize(subID.getNum()+1);
|
||||
assert(mapObjectTypes.at(ID.getNum()));
|
||||
|
||||
JsonUtils::inherit(config, objects.at(ID.getNum())->base);
|
||||
loadSubObject(config.getModScope(), identifier, config, objects.at(ID.getNum()).get(), subID.getNum());
|
||||
if (subID.getNum() >= mapObjectTypes.at(ID.getNum())->objectTypeHandlers.size())
|
||||
{
|
||||
mapObjectTypes.at(ID.getNum())->objectTypeHandlers.resize(subID.getNum() + 1);
|
||||
}
|
||||
|
||||
JsonUtils::inherit(config, mapObjectTypes.at(ID.getNum())->base);
|
||||
loadSubObject(config.getModScope(), identifier, config, mapObjectTypes.at(ID.getNum()).get(), subID.getNum());
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID)
|
||||
{
|
||||
assert(objects.at(ID.getNum()));
|
||||
objects.at(ID.getNum())->objects.at(subID.getNum()) = nullptr;
|
||||
assert(mapObjectTypes.at(ID.getNum()));
|
||||
mapObjectTypes.at(ID.getNum())->objectTypeHandlers.at(subID.getNum()) = nullptr;
|
||||
}
|
||||
|
||||
TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObjectSubID subtype) const
|
||||
{
|
||||
try
|
||||
{
|
||||
if (objects.at(type.getNum()) == nullptr)
|
||||
return objects.front()->objects.front();
|
||||
if (mapObjectTypes.at(type.getNum()) == nullptr)
|
||||
return mapObjectTypes.front()->objectTypeHandlers.front();
|
||||
|
||||
auto subID = subtype.getNum();
|
||||
if (type == Obj::PRISON)
|
||||
subID = 0;
|
||||
auto result = objects.at(type.getNum())->objects.at(subID);
|
||||
auto result = mapObjectTypes.at(type.getNum())->objectTypeHandlers.at(subID);
|
||||
|
||||
if (result != nullptr)
|
||||
return result;
|
||||
@ -365,11 +368,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(const std::string & scop
|
||||
std::optional<si32> id = VLC->identifiers()->getIdentifier(scope, "object", type);
|
||||
if(id)
|
||||
{
|
||||
const auto & object = objects.at(id.value());
|
||||
const auto & object = mapObjectTypes.at(id.value());
|
||||
std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype);
|
||||
|
||||
if (subID)
|
||||
return object->objects.at(subID.value());
|
||||
return object->objectTypeHandlers.at(subID.value());
|
||||
}
|
||||
|
||||
std::string errorString = "Failed to find object of type " + type + "::" + subtype;
|
||||
@ -386,7 +389,7 @@ std::set<MapObjectID> CObjectClassesHandler::knownObjects() const
|
||||
{
|
||||
std::set<MapObjectID> ret;
|
||||
|
||||
for(auto & entry : objects)
|
||||
for(auto & entry : mapObjectTypes)
|
||||
if (entry)
|
||||
ret.insert(entry->id);
|
||||
|
||||
@ -397,13 +400,13 @@ std::set<MapObjectSubID> CObjectClassesHandler::knownSubObjects(MapObjectID prim
|
||||
{
|
||||
std::set<MapObjectSubID> ret;
|
||||
|
||||
if (!objects.at(primaryID.getNum()))
|
||||
if (!mapObjectTypes.at(primaryID.getNum()))
|
||||
{
|
||||
logGlobal->error("Failed to find object %d", primaryID);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for(const auto & entry : objects.at(primaryID.getNum())->objects)
|
||||
for(const auto & entry : mapObjectTypes.at(primaryID.getNum())->objectTypeHandlers)
|
||||
if (entry)
|
||||
ret.insert(entry->subtype);
|
||||
|
||||
@ -436,12 +439,12 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object)
|
||||
|
||||
void CObjectClassesHandler::afterLoadFinalization()
|
||||
{
|
||||
for(auto & entry : objects)
|
||||
for(auto & entry : mapObjectTypes)
|
||||
{
|
||||
if (!entry)
|
||||
continue;
|
||||
|
||||
for(const auto & obj : entry->objects)
|
||||
for(const auto & obj : entry->objectTypeHandlers)
|
||||
{
|
||||
if (!obj)
|
||||
continue;
|
||||
@ -456,7 +459,7 @@ void CObjectClassesHandler::afterLoadFinalization()
|
||||
void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container)
|
||||
{
|
||||
//duplicate existing two-way portals to make reserve for RMG
|
||||
auto& portalVec = container->objects;
|
||||
auto& portalVec = container->objectTypeHandlers;
|
||||
//FIXME: Monoliths in this vector can be already not useful for every terrain
|
||||
const size_t portalCount = portalVec.size();
|
||||
|
||||
@ -500,10 +503,10 @@ std::string CObjectClassesHandler::getObjectName(MapObjectID type, MapObjectSubI
|
||||
if (handler && handler->hasNameTextID())
|
||||
return handler->getNameTranslated();
|
||||
|
||||
if (objects.at(type.getNum()))
|
||||
return objects.at(type.getNum())->getNameTranslated();
|
||||
if (mapObjectTypes.at(type.getNum()))
|
||||
return mapObjectTypes.at(type.getNum())->getNameTranslated();
|
||||
|
||||
return objects.front()->getNameTranslated();
|
||||
return mapObjectTypes.front()->getNameTranslated();
|
||||
}
|
||||
|
||||
SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObjectSubID subtype) const
|
||||
@ -515,27 +518,27 @@ SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObject
|
||||
if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL)
|
||||
subtype = 0;
|
||||
|
||||
if(objects.at(type.getNum()))
|
||||
if(mapObjectTypes.at(type.getNum()))
|
||||
return getHandlerFor(type, subtype)->getSounds();
|
||||
else
|
||||
return objects.front()->objects.front()->getSounds();
|
||||
return mapObjectTypes.front()->objectTypeHandlers.front()->getSounds();
|
||||
}
|
||||
|
||||
std::string CObjectClassesHandler::getObjectHandlerName(MapObjectID type) const
|
||||
{
|
||||
if (objects.at(type.getNum()))
|
||||
return objects.at(type.getNum())->handlerName;
|
||||
if (mapObjectTypes.at(type.getNum()))
|
||||
return mapObjectTypes.at(type.getNum())->handlerName;
|
||||
else
|
||||
return objects.front()->handlerName;
|
||||
return mapObjectTypes.front()->handlerName;
|
||||
}
|
||||
|
||||
std::string CObjectClassesHandler::getJsonKey(MapObjectID type) const
|
||||
{
|
||||
if (objects.at(type.getNum()) != nullptr)
|
||||
return objects.at(type.getNum())->getJsonKey();
|
||||
if (mapObjectTypes.at(type.getNum()) != nullptr)
|
||||
return mapObjectTypes.at(type.getNum())->getJsonKey();
|
||||
|
||||
logGlobal->warn("Unknown object of type %d!", type);
|
||||
return objects.front()->getJsonKey();
|
||||
return mapObjectTypes.front()->getJsonKey();
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -55,7 +55,7 @@ public:
|
||||
std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map
|
||||
|
||||
JsonNode base;
|
||||
std::vector<TObjectTypeHandler> objects;
|
||||
std::vector<TObjectTypeHandler> objectTypeHandlers;
|
||||
|
||||
ObjectClass();
|
||||
~ObjectClass();
|
||||
@ -69,7 +69,7 @@ public:
|
||||
class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyable
|
||||
{
|
||||
/// list of object handlers, each of them handles only one type
|
||||
std::vector< std::unique_ptr<ObjectClass> > objects;
|
||||
std::vector< std::unique_ptr<ObjectClass> > mapObjectTypes;
|
||||
|
||||
/// map that is filled during construction with all known handlers. Not serializeable due to usage of std::function
|
||||
std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;
|
||||
|
@ -238,25 +238,23 @@ CGMarket * MarketInstanceConstructor::createObject(IGameCallback * cb) const
|
||||
return new CGUniversity(cb);
|
||||
}
|
||||
}
|
||||
else if(marketModes.size() == 2)
|
||||
{
|
||||
if(vstd::contains(marketModes, EMarketMode::ARTIFACT_EXP))
|
||||
return new CGArtifactsAltar(cb);
|
||||
}
|
||||
return new CGMarket(cb);
|
||||
}
|
||||
|
||||
void MarketInstanceConstructor::initializeObject(CGMarket * market) const
|
||||
{
|
||||
market->marketModes = marketModes;
|
||||
market->addMarketMode(marketModes);
|
||||
market->marketEfficiency = marketEfficiency;
|
||||
|
||||
market->title = market->getObjectName();
|
||||
if(!title.empty())
|
||||
market->title = VLC->generaltexth->translate(title);
|
||||
|
||||
if (!speech.empty())
|
||||
market->speech = VLC->generaltexth->translate(speech);
|
||||
if(auto university = dynamic_cast<CGUniversity*>(market))
|
||||
{
|
||||
university->title = market->getObjectName();
|
||||
if(!title.empty())
|
||||
university->title = VLC->generaltexth->translate(title);
|
||||
|
||||
if(!speech.empty())
|
||||
university->speech = VLC->generaltexth->translate(speech);
|
||||
}
|
||||
}
|
||||
|
||||
void MarketInstanceConstructor::randomizeObject(CGMarket * object, vstd::RNG & rng) const
|
||||
|
@ -23,6 +23,11 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
ObjectInstanceID CGMarket::getObjInstanceID() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
void CGMarket::initObj(vstd::RNG & rand)
|
||||
{
|
||||
getObjectHandler()->configureObject(this, rand);
|
||||
@ -38,23 +43,11 @@ int CGMarket::getMarketEfficiency() const
|
||||
return marketEfficiency;
|
||||
}
|
||||
|
||||
bool CGMarket::allowsTrade(EMarketMode mode) const
|
||||
{
|
||||
return marketModes.count(mode);
|
||||
}
|
||||
|
||||
int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<TradeItemBuy> CGMarket::availableItemsIds(EMarketMode mode) const
|
||||
{
|
||||
if(allowsTrade(mode))
|
||||
return IMarket::availableItemsIds(mode);
|
||||
return std::vector<TradeItemBuy>();
|
||||
}
|
||||
|
||||
CGMarket::CGMarket(IGameCallback *cb):
|
||||
CGObjectInstance(cb)
|
||||
{}
|
||||
@ -63,8 +56,6 @@ std::vector<TradeItemBuy> CGBlackMarket::availableItemsIds(EMarketMode mode) con
|
||||
{
|
||||
switch(mode)
|
||||
{
|
||||
case EMarketMode::ARTIFACT_RESOURCE:
|
||||
return IMarket::availableItemsIds(mode);
|
||||
case EMarketMode::RESOURCE_ARTIFACT:
|
||||
{
|
||||
std::vector<TradeItemBuy> ret;
|
||||
@ -113,9 +104,4 @@ void CGUniversity::onHeroVisit(const CGHeroInstance * h) const
|
||||
cb->showObjectWindow(this, EOpenWindowMode::UNIVERSITY_WINDOW, h, true);
|
||||
}
|
||||
|
||||
ArtBearer::ArtBearer CGArtifactsAltar::bearerType() const
|
||||
{
|
||||
return ArtBearer::ALTAR;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -18,32 +18,36 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket
|
||||
{
|
||||
public:
|
||||
|
||||
std::set<EMarketMode> marketModes;
|
||||
int marketEfficiency;
|
||||
|
||||
//window variables
|
||||
std::string title;
|
||||
std::string speech; //currently shown only in university
|
||||
|
||||
CGMarket(IGameCallback *cb);
|
||||
///IObjectInterface
|
||||
void onHeroVisit(const CGHeroInstance * h) const override; //open trading window
|
||||
void initObj(vstd::RNG & rand) override;//set skills for trade
|
||||
|
||||
///IMarket
|
||||
ObjectInstanceID getObjInstanceID() const override;
|
||||
int getMarketEfficiency() const override;
|
||||
bool allowsTrade(EMarketMode mode) const override;
|
||||
int availableUnits(EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited
|
||||
std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<CGObjectInstance&>(*this);
|
||||
h & marketModes;
|
||||
h & static_cast<IMarket&>(*this);
|
||||
h & marketEfficiency;
|
||||
h & title;
|
||||
h & speech;
|
||||
if (h.version < Handler::Version::NEW_MARKETS)
|
||||
{
|
||||
std::string speech;
|
||||
std::string title;
|
||||
h & speech;
|
||||
h & title;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Handler> void serializeArtifactsAltar(Handler &h)
|
||||
{
|
||||
serialize(h);
|
||||
IMarket::serializeArtifactsAltar(h);
|
||||
}
|
||||
};
|
||||
|
||||
@ -68,6 +72,8 @@ class DLL_LINKAGE CGUniversity : public CGMarket
|
||||
{
|
||||
public:
|
||||
using CGMarket::CGMarket;
|
||||
std::string speech; //currently shown only in university
|
||||
std::string title;
|
||||
|
||||
std::vector<TradeItemBuy> skills; //available skills
|
||||
|
||||
@ -78,20 +84,11 @@ public:
|
||||
{
|
||||
h & static_cast<CGMarket&>(*this);
|
||||
h & skills;
|
||||
}
|
||||
};
|
||||
|
||||
class DLL_LINKAGE CGArtifactsAltar : public CGMarket, public CArtifactSet
|
||||
{
|
||||
public:
|
||||
using CGMarket::CGMarket;
|
||||
|
||||
ArtBearer::ArtBearer bearerType() const override;
|
||||
|
||||
template <typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & static_cast<CGMarket&>(*this);
|
||||
h & static_cast<CArtifactSet&>(*this);
|
||||
if (h.version >= Handler::Version::NEW_MARKETS)
|
||||
{
|
||||
h & speech;
|
||||
h & title;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -690,35 +690,6 @@ int CGTownInstance::getMarketEfficiency() const
|
||||
return marketCount;
|
||||
}
|
||||
|
||||
bool CGTownInstance::allowsTrade(EMarketMode mode) const
|
||||
{
|
||||
switch(mode)
|
||||
{
|
||||
case EMarketMode::RESOURCE_RESOURCE:
|
||||
case EMarketMode::RESOURCE_PLAYER:
|
||||
return hasBuilt(BuildingID::MARKETPLACE);
|
||||
|
||||
case EMarketMode::ARTIFACT_RESOURCE:
|
||||
case EMarketMode::RESOURCE_ARTIFACT:
|
||||
return hasBuilt(BuildingSubID::ARTIFACT_MERCHANT);
|
||||
|
||||
case EMarketMode::CREATURE_RESOURCE:
|
||||
return hasBuilt(BuildingSubID::FREELANCERS_GUILD);
|
||||
|
||||
case EMarketMode::CREATURE_UNDEAD:
|
||||
return hasBuilt(BuildingSubID::CREATURE_TRANSFORMER);
|
||||
|
||||
case EMarketMode::RESOURCE_SKILL:
|
||||
return hasBuilt(BuildingSubID::MAGIC_UNIVERSITY);
|
||||
case EMarketMode::CREATURE_EXP:
|
||||
case EMarketMode::ARTIFACT_EXP:
|
||||
return false;
|
||||
default:
|
||||
assert(0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TradeItemBuy> CGTownInstance::availableItemsIds(EMarketMode mode) const
|
||||
{
|
||||
if(mode == EMarketMode::RESOURCE_ARTIFACT)
|
||||
@ -739,6 +710,11 @@ std::vector<TradeItemBuy> CGTownInstance::availableItemsIds(EMarketMode mode) co
|
||||
return IMarket::availableItemsIds(mode);
|
||||
}
|
||||
|
||||
ObjectInstanceID CGTownInstance::getObjInstanceID() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
void CGTownInstance::updateAppearance()
|
||||
{
|
||||
auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId();
|
||||
@ -932,12 +908,7 @@ const CArmedInstance * CGTownInstance::getUpperArmy() const
|
||||
|
||||
bool CGTownInstance::hasBuiltSomeTradeBuilding() const
|
||||
{
|
||||
for(const auto & bid : builtBuildings)
|
||||
{
|
||||
if(town->buildings.at(bid)->IsTradeBuilding())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return availableModes().empty() ? false : true;
|
||||
}
|
||||
|
||||
bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
|
||||
@ -962,6 +933,50 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) c
|
||||
return false;
|
||||
}
|
||||
|
||||
void CGTownInstance::addBuilding(const BuildingID & buildingID)
|
||||
{
|
||||
if(buildingID == BuildingID::NONE)
|
||||
return;
|
||||
|
||||
const auto townType = (*VLC->townh)[getFaction()]->town;
|
||||
if(const auto & building = townType->buildings.find(buildingID); building != townType->buildings.end())
|
||||
{
|
||||
builtBuildings.insert(buildingID);
|
||||
addMarketMode(building->second->marketModes);
|
||||
}
|
||||
}
|
||||
|
||||
void CGTownInstance::postDeserializeMarketFix()
|
||||
{
|
||||
// re-add all buildings to recreate existing market modes
|
||||
auto buildingsBak = builtBuildings;
|
||||
for (auto building : buildingsBak)
|
||||
addBuilding(building);
|
||||
}
|
||||
|
||||
void CGTownInstance::removeBuilding(const BuildingID & buildingID)
|
||||
{
|
||||
if(!vstd::contains(builtBuildings, buildingID))
|
||||
return;
|
||||
|
||||
if(const auto & building = town->buildings.find(buildingID); building != town->buildings.end())
|
||||
{
|
||||
builtBuildings.erase(buildingID);
|
||||
removeMarketMode(building->second->marketModes);
|
||||
}
|
||||
}
|
||||
|
||||
void CGTownInstance::removeAllBuildings()
|
||||
{
|
||||
builtBuildings.clear();
|
||||
removeAllMarketModes();
|
||||
}
|
||||
|
||||
std::set<BuildingID> CGTownInstance::getBuildings() const
|
||||
{
|
||||
return builtBuildings;
|
||||
}
|
||||
|
||||
TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
|
||||
{
|
||||
if (vstd::contains(town->buildings, buildingID))
|
||||
@ -1132,23 +1147,23 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
{
|
||||
handler.serializeLIC("buildings", buildingsLIC);
|
||||
|
||||
builtBuildings.insert(BuildingID::VILLAGE_HALL);
|
||||
addBuilding(BuildingID::VILLAGE_HALL);
|
||||
|
||||
if(buildingsLIC.none.empty() && buildingsLIC.all.empty())
|
||||
{
|
||||
builtBuildings.insert(BuildingID::DEFAULT);
|
||||
addBuilding(BuildingID::DEFAULT);
|
||||
|
||||
bool hasFort = false;
|
||||
handler.serializeBool("hasFort",hasFort);
|
||||
if(hasFort)
|
||||
builtBuildings.insert(BuildingID::FORT);
|
||||
addBuilding(BuildingID::FORT);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(const si32 item : buildingsLIC.none)
|
||||
forbiddenBuildings.insert(BuildingID(item));
|
||||
for(const si32 item : buildingsLIC.all)
|
||||
builtBuildings.insert(BuildingID(item));
|
||||
addBuilding(BuildingID(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public I
|
||||
std::string nameTextId; // name of town
|
||||
|
||||
std::map<BuildingID, TownRewardableBuildingInstance*> convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector);
|
||||
std::set<BuildingID> builtBuildings;
|
||||
|
||||
public:
|
||||
using CGDwelling::getPosition;
|
||||
|
||||
@ -65,7 +67,6 @@ public:
|
||||
ui32 identifier; //special identifier from h3m (only > RoE maps)
|
||||
PlayerColor alignmentToPlayer; // if set to non-neutral, random town will have same faction as specified player
|
||||
std::set<BuildingID> forbiddenBuildings;
|
||||
std::set<BuildingID> builtBuildings;
|
||||
std::map<BuildingID, TownRewardableBuildingInstance*> rewardableBuildings;
|
||||
std::vector<SpellID> possibleSpells, obligatorySpells;
|
||||
std::vector<std::vector<SpellID> > spells; //spells[level] -> vector of spells, first will be available in guild
|
||||
@ -76,6 +77,9 @@ public:
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
h & static_cast<CGDwelling&>(*this);
|
||||
if (h.version >= Handler::Version::NEW_MARKETS)
|
||||
h & static_cast<IMarket&>(*this);
|
||||
|
||||
h & nameTextId;
|
||||
h & built;
|
||||
h & destroyed;
|
||||
@ -114,6 +118,9 @@ public:
|
||||
town = faction ? faction->town : nullptr;
|
||||
}
|
||||
|
||||
if (!h.saving && h.version < Handler::Version::NEW_MARKETS)
|
||||
postDeserializeMarketFix();
|
||||
|
||||
h & townAndVis;
|
||||
BONUS_TREE_DESERIALIZATION_FIX
|
||||
|
||||
@ -133,6 +140,7 @@ public:
|
||||
void updateMoraleBonusFromArmy() override;
|
||||
void deserializationFix();
|
||||
void postDeserialize();
|
||||
void postDeserializeMarketFix();
|
||||
void recreateBuildingsBonuses();
|
||||
void setVisitingHero(CGHeroInstance *h);
|
||||
void setGarrisonedHero(CGHeroInstance *h);
|
||||
@ -152,9 +160,8 @@ public:
|
||||
EGeneratorState shipyardStatus() const override;
|
||||
const IObjectInterface * getObject() const override;
|
||||
int getMarketEfficiency() const override; //=market count
|
||||
bool allowsTrade(EMarketMode mode) const override;
|
||||
std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const override;
|
||||
|
||||
ObjectInstanceID getObjInstanceID() const override;
|
||||
void updateAppearance();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@ -174,6 +181,10 @@ public:
|
||||
//checks if building is constructed and town has same subID
|
||||
bool hasBuilt(const BuildingID & buildingID) const;
|
||||
bool hasBuilt(const BuildingID & buildingID, FactionID townID) const;
|
||||
void addBuilding(const BuildingID & buildingID);
|
||||
void removeBuilding(const BuildingID & buildingID);
|
||||
void removeAllBuildings();
|
||||
std::set<BuildingID> getBuildings() const;
|
||||
|
||||
TResources getBuildingCost(const BuildingID & buildingID) const;
|
||||
TResources dailyIncome() const; //calculates daily income of this town
|
||||
|
@ -20,6 +20,11 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
bool IMarket::allowsTrade(const EMarketMode mode) const
|
||||
{
|
||||
return vstd::contains(marketModes, mode);
|
||||
}
|
||||
|
||||
bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const
|
||||
{
|
||||
switch(mode)
|
||||
@ -122,12 +127,7 @@ bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IMarket::allowsTrade(EMarketMode mode) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int IMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
|
||||
int IMarket::availableUnits(const EMarketMode mode, const int marketItemSerial) const
|
||||
{
|
||||
switch(mode)
|
||||
{
|
||||
@ -140,7 +140,45 @@ int IMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TradeItemBuy> IMarket::availableItemsIds(EMarketMode mode) const
|
||||
void IMarket::addMarketMode(const EMarketMode mode)
|
||||
{
|
||||
marketModes.insert(mode);
|
||||
|
||||
if(mode == EMarketMode::ARTIFACT_EXP)
|
||||
altarArtifactsStorage = std::make_shared<CArtifactSetAltar>();
|
||||
}
|
||||
|
||||
void IMarket::addMarketMode(const std::set<EMarketMode> & modes)
|
||||
{
|
||||
for(const auto & mode : modes)
|
||||
addMarketMode(mode);
|
||||
}
|
||||
|
||||
void IMarket::removeMarketMode(const EMarketMode mode)
|
||||
{
|
||||
marketModes.erase(mode);
|
||||
|
||||
if(mode == EMarketMode::ARTIFACT_EXP)
|
||||
altarArtifactsStorage.reset();
|
||||
}
|
||||
|
||||
void IMarket::removeMarketMode(const std::set<EMarketMode> & modes)
|
||||
{
|
||||
for(const auto & mode : modes)
|
||||
removeMarketMode(mode);
|
||||
}
|
||||
|
||||
void IMarket::removeAllMarketModes()
|
||||
{
|
||||
marketModes.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<CArtifactSet> IMarket::getArtifactsStorage() const
|
||||
{
|
||||
return altarArtifactsStorage;
|
||||
}
|
||||
|
||||
std::vector<TradeItemBuy> IMarket::availableItemsIds(const EMarketMode mode) const
|
||||
{
|
||||
std::vector<TradeItemBuy> ret;
|
||||
switch(mode)
|
||||
@ -148,24 +186,15 @@ std::vector<TradeItemBuy> IMarket::availableItemsIds(EMarketMode mode) const
|
||||
case EMarketMode::RESOURCE_RESOURCE:
|
||||
case EMarketMode::ARTIFACT_RESOURCE:
|
||||
case EMarketMode::CREATURE_RESOURCE:
|
||||
for (auto res : GameResID::ALL_RESOURCES())
|
||||
for(const auto & res : GameResID::ALL_RESOURCES())
|
||||
ret.push_back(res);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
IMarket::IMarket()
|
||||
std::set<EMarketMode> IMarket::availableModes() const
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<EMarketMode> IMarket::availableModes() const
|
||||
{
|
||||
std::vector<EMarketMode> ret;
|
||||
for (EMarketMode i = static_cast<EMarketMode>(0); i < EMarketMode::MARKET_AFTER_LAST_PLACEHOLDER; i = vstd::next(i, 1))
|
||||
if(allowsTrade(i))
|
||||
ret.push_back(i);
|
||||
|
||||
return ret;
|
||||
return marketModes;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -11,24 +11,54 @@
|
||||
|
||||
#include "../networkPacks/TradeItem.h"
|
||||
#include "../constants/Enumerations.h"
|
||||
#include "../CArtHandler.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CGObjectInstance;
|
||||
|
||||
class DLL_LINKAGE IMarket
|
||||
class DLL_LINKAGE IMarket : public virtual Serializeable
|
||||
{
|
||||
public:
|
||||
IMarket();
|
||||
virtual ~IMarket() {}
|
||||
class CArtifactSetAltar : public CArtifactSet
|
||||
{
|
||||
public:
|
||||
ArtBearer::ArtBearer bearerType() const override {return ArtBearer::ALTAR;};
|
||||
};
|
||||
|
||||
virtual ObjectInstanceID getObjInstanceID() const = 0; // The market is always an object on the map
|
||||
virtual int getMarketEfficiency() const = 0;
|
||||
virtual bool allowsTrade(EMarketMode mode) const;
|
||||
virtual int availableUnits(EMarketMode mode, int marketItemSerial) const; //-1 if unlimited
|
||||
virtual std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const;
|
||||
|
||||
virtual bool allowsTrade(const EMarketMode mode) const;
|
||||
virtual int availableUnits(const EMarketMode mode, const int marketItemSerial) const; //-1 if unlimited
|
||||
virtual std::vector<TradeItemBuy> availableItemsIds(const EMarketMode mode) const;
|
||||
void addMarketMode(const EMarketMode mode);
|
||||
void addMarketMode(const std::set<EMarketMode> & modes);
|
||||
void removeMarketMode(const EMarketMode mode);
|
||||
void removeMarketMode(const std::set<EMarketMode> & modes);
|
||||
void removeAllMarketModes();
|
||||
std::set<EMarketMode> availableModes() const;
|
||||
std::shared_ptr<CArtifactSet> getArtifactsStorage() const;
|
||||
bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units
|
||||
std::vector<EMarketMode> availableModes() const;
|
||||
|
||||
template <typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
h & marketModes;
|
||||
|
||||
if(vstd::contains(marketModes, EMarketMode::ARTIFACT_EXP))
|
||||
{
|
||||
if (!h.saving)
|
||||
altarArtifactsStorage = std::make_shared<CArtifactSetAltar>();
|
||||
|
||||
h & *altarArtifactsStorage;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Handler> void serializeArtifactsAltar(Handler & h)
|
||||
{
|
||||
h & *altarArtifactsStorage;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<CArtifactSetAltar> altarArtifactsStorage;
|
||||
std::set<EMarketMode> marketModes;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -92,18 +92,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/// Compatibility for old code
|
||||
class DLL_LINKAGE CTownCompatBuilding1 : public TownRewardableBuildingInstance
|
||||
{
|
||||
public:
|
||||
using TownRewardableBuildingInstance::TownRewardableBuildingInstance;
|
||||
};
|
||||
|
||||
/// Compatibility for old code
|
||||
class DLL_LINKAGE CTownCompatBuilding2 : public TownRewardableBuildingInstance
|
||||
{
|
||||
public:
|
||||
using TownRewardableBuildingInstance::TownRewardableBuildingInstance;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user