1
0
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:
Simeon Manolov 2024-08-27 03:05:07 +03:00
commit e9131538dd
No known key found for this signature in database
GPG Key ID: 0AAC6D4A304B5635
161 changed files with 2008 additions and 1877 deletions

View File

@ -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'

View File

@ -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;

View File

@ -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);

View File

@ -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())
{

View File

@ -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);
};

View File

@ -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;
}

View File

@ -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);

View File

@ -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)
{

View File

@ -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;

View File

@ -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());
}

View File

@ -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();

View File

@ -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;

View File

@ -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());
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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" : "与此建筑冲突:",

View File

@ -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",

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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"

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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();

View File

@ -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;

View File

@ -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));

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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();

View File

@ -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();

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -166,7 +166,7 @@
"townHall": { },
"cityHall": { },
"capitol": { },
"marketplace": { },
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
"resourceSilo": { "produce": { "ore": 1, "wood": 1 } },
"blacksmith": { },

View File

@ -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" },

View File

@ -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": {

View File

@ -165,7 +165,7 @@
"townHall": { },
"cityHall": { },
"capitol": { },
"marketplace": { },
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
"resourceSilo": { "produce": { "wood": 1, "ore": 1 } },
"blacksmith": { },

View File

@ -167,7 +167,7 @@
"townHall": { },
"cityHall": { },
"capitol": { },
"marketplace": { },
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
"resourceSilo": { "produce": { "mercury": 1 } },
"blacksmith": { },

View File

@ -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" } ] },

View File

@ -170,7 +170,7 @@
"townHall": { },
"cityHall": { },
"capitol": { },
"marketplace": { },
"marketplace": { "marketModes" : ["resource-resource", "resource-player"] },
"resourceSilo": { "produce": { "crystal": 1 } },
"blacksmith": { },

View File

@ -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" ],

View File

@ -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" ] },

View File

@ -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
View File

@ -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

View File

@ -1,7 +1,7 @@
[![VCMI](https://github.com/vcmi/vcmi/actions/workflows/github.yml/badge.svg?branch=develop&event=push)](https://github.com/vcmi/vcmi/actions/workflows/github.yml?query=branch%3Adevelop+event%3Apush)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.0/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.0)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.5/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.5)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.6/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.6)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/1.5.7/total)](https://github.com/vcmi/vcmi/releases/tag/1.5.7)
[![Github Downloads](https://img.shields.io/github/downloads/vcmi/vcmi/total)](https://github.com/vcmi/vcmi/releases)
# VCMI Project

View File

@ -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"] },
```

View File

@ -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"/>

View File

@ -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);

View File

@ -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;

View File

@ -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"

View File

@ -53,8 +53,6 @@ class CStack;
class CCreature;
class CLoadFile;
class CSaveFile;
class BinaryDeserializer;
class BinarySerializer;
class BattleStateInfo;
struct ArtifactLocation;
class BattleStateInfoForRetreat;

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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){};

View File

@ -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;
}

View File

@ -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

View File

@ -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 &params) const

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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());
}

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;
}
}
};

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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